⚡️ 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).

components/GoalItem.js
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

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.

GoalItem.js
//...
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>
  )
}

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.

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

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

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.

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]

App.js
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;

To checkout a live version of this Todo App, you can refer to this Snack project.

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.

Last updated