> For the complete documentation index, see [llms.txt](https://sydrawat.gitbook.io/react-native/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sydrawat.gitbook.io/react-native/basics/custom-components.md).

# ⚡️ Custom Components

Let's go one step ahead and create our custom components for react native applications!

## Split App into Components

Let's split the previously built `Todo App` into separate components, to make our app more modular. It's fairly simple if you come from a React background for web applications.

Let's start by making a separate folder called `components` which will hold our individual components for the react native app. We follow the usual naming convention of using a capital letter to name the component here as well (same in React web apps).

{% tabs %}
{% tab title="GoalItem" %}
{% code title="components/GoalItem.js" %}

```jsx
import React from 'react'
import {StyleSheet, Text, View} from 'react-native'

const GoalItem = (props) => {
  return (
    <View style={styles.goalItem}>
      <Text>{props.title}</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  goalItem: {
    padding: 10,
    marginVertical: 10,
    borderWidth: 1,
    borderColor: 'black',
    backgroundColor: '#ccc'
  }
})

export default GoalItem
```

{% endcode %}
{% endtab %}

{% tab title="GoalInput" %}
{% code title="components/GoalInput.js" %}

```jsx
import React, {useState} from 'react'
import {StyleSheet, TextInput, View, Button} from 'react-native'

const GoalInput = (props) => {
  const [newGoal, setNewGoal] = useState('')
  const goalInputHandler = (goal) => {
    setNewGoal(goal)
  }
  
  return (
    <View style={styles.inputContainer}>
      <TextInput
        style={styles.input}
        placeholder="Todo Goals"
        onChange={goalInputHandler}
        value={newGoal}
      />
      <Button title="ADD" onPress={props.addGoal.bind(this, newGoal)}/>
    </View>
  )
}

const styles = StyleSheet.create({
  inputContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center'
  },
  input: {
    borderColor: 'black',
    borderWidth: 1,
    padding: 10,
    width: '80%'
  }
})

export default GoalInput
```

{% endcode %}
{% endtab %}

{% tab title="App.js" %}
{% code title="App.js" %}

```jsx
import React, {useState} from 'react';
import {StyleSheet, View, FlatList} from 'react-native'
import {StatusBar} from 'expo-status-bar'

import GoalInput from './components/GoalInput'
import GoalItem from './components/GoalItem'
const App = () => {
  const [goals, setGoals] = useState([])
  
  const addGoalHandler = (goalTitle) => {
    setGoals((prevState) => [
      ...prevState,
      {uid: Math.random().toString(), value: goalTitle}
    ])
  }
  
  return (
    <View>
      <GoalInput addGoal={addGoalHandler}/>
      <FlatList
        keyExtractor={(item, index) => item.uid}
        data={goals}
        renderItem={itemData => <GoalItem title={itemData.item.uid} />}
      />
      <StatusBar style="auto"/>
    </View>
  )
}

const styles = StyleSheet.create({
  root: { padding: 50 }
})
export default App
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Touchable Components

The `View` component has many different event listeners like `onTouchStart`, `onTouchEnd`, etc. To use all these event listeners manually, would be a huge task to perform a very rudimentary event. This is where react native provides us with a custom component called `Touchable`, which helps us overcome this issue.

The `Touchable` component does not render anything onto the app, but rather it registers an event listener to the child component that it wraps. This component then gives us more detailed touch events which are pre-configured for us.

Now to be specific, `Touchable` cannot be used as a component, rather it acts more as a parent class for React Native because there are multiple specific versions of `Touchable` that we should actually use. For example, `TouchableOpacity` is now a component that we can use instead of `Touchable`. So when we try to add event listeners to `TouchableOpacity`, we see that our code editor (VSCode here) gives us auto-complete suggestions like `onPress`, `onLongPress`, etc., which are way more useful.

`TouchableOpacity` has an `activeOpacity` property, though which we can control the opacity of the component that it wraps. It accepts a number between `0` and `1`.

We have another component called `TouchableHighlight`, which is almost similar to `TouchableOpacity` as it helps us use event listeners. This component adds a background color to the child component, whenever there is a touch event on the child component itself.

We can change the background *"highlight"* color of the child component using `underlayColor` property. It accepts a string that has a color value, like '**#ccc**' or '**grey**', etc.

Let's see it in action on our Todo App, where we will delete a goal item when we touch the goal item itself.

{% tabs %}
{% tab title="TouchableOpacity" %}
{% code title="GoalItem.js" %}

```jsx
//...
import {..., TouchableOpacity} from 'react-native'

const GoalItem = (props) => {
  return (
    <TouchableOpacity activeOpacity={0.8} onPress={props.deleteGoal}>
      <View style={styles.goalItem}>
        <Text>{props.title}</Text>
      </View>
    </TouchableOpacity>
  )
}
```

{% endcode %}
{% endtab %}

{% tab title="TouchableHighlight" %}
{% code title="GoalItem.js" %}

```jsx
//...
import {..., TouchableHighlight} from 'react-native'
const GoalItem = (props) => {
  return (
    <TouchableHighlight underlayColor="#ccc" onPress={props.deleteGoal}>
      <View style={styles.goalItem}>
        <Text>{props.title}</Text>
      </View>
    </TouchableHighlight>
  )
}
```

{% endcode %}
{% endtab %}

{% tab title="deleteGoal" %}
{% code title="App.js" %}

```jsx
//...
const deleteGoalHandler = (id) => {
  setGoals((prevState) => prevState.filter(goal => goal.uid !== id))
}

//...
      <FlatList
        keyExtractor={(item, index) => item.uid}
        data={courseGoals}
        renderItem={(itemData) => (
          <GoalItem
            deleteGoal={deleteGoalHandler.bind(this, itemData.item.uid)}
            title={itemData.item.value}
          />
        )}
      />
```

{% endcode %}
{% endtab %}
{% endtabs %}

Other options available for listening to events are:

* `TouchableNativeFeedback`: This is available only on android devices, where on touch generates a 'ripple' effect.
* `TouchableWithoutFeedback`: This is available only on android devices, where on touch does not generate any visual effect.

These `Touchable` components are really important in react native because they allow us to attach the normal high level listeners, like `onPress`, `onLongPress`, etc., to any component in react native, and with that we can build our own touchable components, buttons,  links, etc.

## Modal Overlay

Let's add a modal overlay to our Todo App, where we get a modal in which we can enter our Todo goal, and only display the list of todos and a  button to add the new Todo. The button press event will trigger the modal overlay here.

React native has an inbuilt component called `Modal`, which provides us with a default `Modal` component, and we can add animation styles to it using the `animationType` prop to it. To show and hide the `Modal`, we have another prop called `visible`, which is a boolean that tells the `Modal` whether or not to show the modal. We will manage this using a state in our application.

### Creating the Modal

{% tabs %}
{% tab title="App.js" %}
{% code title="App.js" %}

```jsx
//...
const App = () => {
  //...
  const [isAddMode, setIsAddMode] = useState(false)
  //...
  return (
    <View>
      <Button title="Add New Goal" onPress={() => setAddMode(true)}/>
      <GoalInput addGoal={addGoalHandler} visiblity={isAddMode}/>
      //...
    </View>
  )
}
//...
```

{% endcode %}
{% endtab %}

{% tab title="GoalInput" %}
{% code title="components/GoalInput.js" %}

```jsx
//...
import {Modal, ...} from 'react-native'

const GoalInput = (props) => {
  //...
  return (
    <Modal visible={props.visibility} animationType="slide">
      <View style={styles.inputContainer}>
        //...
        <TextInput style={styles.input}/>
        <Button/>
        //...
      </View>
    </Modal>
  )
}

const styles = StyleSheet.create({
  inputContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  input: {
    //...
    marginBottom: 10
  }
})
```

{% endcode %}
{% endtab %}
{% endtabs %}

With this in place, we can see the `Modal` component working as expected on button press event. Now, we need to close the modal once the  user is done adding the new goal to the list.

### Closing the Modal

Now, let's close the `Modal`, and clear the input from the `TextInput` component in our `GoalInput` component. Let's also add a "Cancel" button, to cancel adding a new goal to the list. Below is the complete code for the `App` and `GoalInput` components.

{% hint style="warning" %}
We cannot at a `style` property to a `Button` component in react native. Instead, we need to wrap our `Button` within a `View` and then apply the styles to the  `Button` via this `View` component. \[Please refer below code for an example]
{% endhint %}

{% tabs %}
{% tab title="App.js" %}
{% code title="App.js" %}

```jsx
import { StatusBar } from 'expo-status-bar';
import React, { useState } from 'react';
import { StyleSheet, View, FlatList, Button } from 'react-native';

import GoalItem from './components/GoalItem';
import GoalInput from './components/GoalInput';

const App = () => {
  const [courseGoals, setCourseGoals] = useState([]);
  const [isAddMode, setIsAddMode] = useState(false);

  const addGoalHandler = (goalTitle) => {
    setCourseGoals((prevState) => [
      ...prevState,
      { uid: Math.random().toString(), value: goalTitle },
    ]);
    setIsAddMode(false);
  };

  const deleteGoalHandler = (id) => {
    setCourseGoals((prevState) => prevState.filter((goal) => goal.uid !== id));
  };

  const cancelGoalHandler = () => {
    setIsAddMode(false);
  };

  return (
    <View style={styles.root}>
      <Button
        title="Add New Goal"
        onPress={() => {
          setIsAddMode(true);
        }}
      />
      <GoalInput
        visibility={isAddMode}
        addGoal={addGoalHandler}
        onCancel={cancelGoalHandler}
      />
      <FlatList
        keyExtractor={(item, index) => item.uid}
        data={courseGoals}
        renderItem={(itemData) => (
          <GoalItem
            deleteGoal={deleteGoalHandler.bind(this, itemData.item.uid)}
            title={itemData.item.value}
          />
        )}
      />
      <StatusBar style="auto" />
    </View>
  );
};

const styles = StyleSheet.create({
  root: { padding: 50 },
});

export default App;

```

{% endcode %}
{% endtab %}

{% tab title="GoalInput" %}
{% code title="components/GoalInput.js" %}

```jsx
import React, { useState } from 'react';
import { TextInput, View, Button, StyleSheet, Modal } from 'react-native';

const GoalInput = (props) => {
  const [newGoal, setNewGoal] = useState('');

  const goalInputHandler = (newGoal) => {
    setNewGoal(newGoal);
  };

  const onAddGoal = () => {
    props.addGoal(newGoal);
    setNewGoal('');
  };

  const cancelHandler = () => {
    props.onCancel();
    setNewGoal('');
  };

  return (
    <Modal visible={props.visibility} animationType="slide">
      <View style={styles.inputContainer}>
        <TextInput
          placeholder="Todo Goals"
          style={styles.input}
          onChangeText={goalInputHandler}
          value={newGoal}
        />
        <View style={styles.buttonContainer}>
          <View style={styles.button}>
            <Button title="Add" onPress={onAddGoal} />
          </View>
          <View style={styles.button}>
            <Button title="Cancel" color="red" onPress={cancelHandler} />
          </View>
        </View>
      </View>
    </Modal>
  );
};

const styles = StyleSheet.create({
  inputContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  input: {
    borderColor: 'black',
    borderWidth: 1,
    padding: 10,
    width: '80%',
    marginBottom: 10,
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
    width: '60%',
  },
  button: {
    width: '40%',
  },
});

export default GoalInput;

```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="success" %}
To checkout a live version of this Todo App, you can refer to [**this Snack project**](https://snack.expo.io/@sydrawat/todo-app)**.**

> The styling and a few minor things might be different in the Snack Project and the actual code/snippets that I have provided here, but  the overall idea and functionality is the same.
> {% endhint %}
