Mastering Reusability in React Native

Mastering Reusability in React Native

Are you frustrated with the constant need to write repetitive code and manage values manually? It’s time to embrace one of the core principles of programming: DRY (Don’t Repeat Yourself). However, this raises the question—how can you avoid repetition when the same functionality is required multiple times?

Let’s proceed with the Counter Application example. You have the option to either follow the tutorial from the beginning or clone the project and start from this point.

In the Counter application, suppose we decide to include two counters—one at the top and another at the bottom. We could either duplicate the existing counter and style it accordingly, or we could create a reusable component that can be utilized multiple times on a single screen. Let’s take the latter approach.

Structuring the application

Let’s begin by organizing things in a logical and easy-to-find manner. First, create a folder named src in the root of the project, and within that, create another folder called components since the counter is a reusable component. You can create these folders manually or by using the following command.

mkdir -p src/components

Isolating the Counter

Next, create a folder named Counter inside the newly created components folder, and then add two files within the Counter folder.

  • index.tsx – The entry file for the component
  • styles.ts – Stylesheet for the counter

Using the following command to create the Counter folder:

mkdir src/components/Counter

Using the following command let’s create the necessary files:

touch src/components/Counter/index.tsx src/components/Counter/styles.ts

Creating the Counter functional component

Now, let’s set up the boilerplate code for the Counter functional component. Add the following code to the index.tsx file located in the src/components/Counter folder.

import React from "react";

const Counter: React.FC = (): React.JSX.Element => {
    return <></>
}

export default Counter;

Copy the original component

Currently, the code in the index.tsx file doesn’t properly represent the Counter as needed. We’ll need to copy it from App.tsx. Please move the following lines from App.tsx to src/components/Counter/index.tsx.

App.tsx – Copy from Line 21 to 29
import { StatusBar } from "expo-status-bar";
import { useState } from "react";
import { Button, StyleSheet, Text, View } from "react-native";

export default function App() {
  const [count, setCount] = useState(0);

  function increment() {
    setCount((prevState) => prevState + 1);
  }

  function decrement() {
    setCount((prevState) => prevState - 1);
  }

  function reset() {
    setCount(0);
  }

  return (
    <View style={styles.container}>
      <Text style={styles.count}>{count}</Text>
      <View style={styles.actionWrapper}>
        <Button title="Decrease" onPress={decrement} disabled={count === 0} />
        <Button title="Increase" onPress={increment} />
      </View>
      <Button title="Reset" onPress={reset} disabled={count === 0} />
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
    rowGap: 24,
  },
  actionWrapper: {
    flexDirection: "row",
    columnGap: 18,
  },
  count: {
    fontSize: 24,
    fontWeight: "bold",
  },
});
index.tsx – src/components/Counter
import React from "react";
import {Button, Text, View} from "react-native";

const Counter: React.FC = (): React.JSX.Element => {
  return (
    <View style={styles.container}>
      <Text style={styles.count}>{count}</Text>
      <View style={styles.actionWrapper}>
        <Button title="Decrease" onPress={decrement} disabled={count === 0} />
        <Button title="Increase" onPress={increment} />
      </View>
      <Button title="Reset" onPress={reset} disabled={count === 0} />
    </View>
  )
}

export default Counter;

Copy styles into styles.ts

We’re seeing errors related to the styles variable, which makes sense since we haven’t defined styles for the counter yet. To fix this, let’s copy the necessary styles into the styles.ts file.

App.tsx
import { StatusBar } from "expo-status-bar";
import { useState } from "react";
import { Button, StyleSheet, Text, View } from "react-native";

export default function App() {
  const [count, setCount] = useState(0);

  function increment() {
    setCount((prevState) => prevState + 1);
  }

  function decrement() {
    setCount((prevState) => prevState - 1);
  }

  function reset() {
    setCount(0);
  }

  return (
    <View style={styles.container}>
      <Text style={styles.count}>{count}</Text>
      <View style={styles.actionWrapper}>
        <Button title="Decrease" onPress={decrement} disabled={count === 0} />
        <Button title="Increase" onPress={increment} />
      </View>
      <Button title="Reset" onPress={reset} disabled={count === 0} />
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
    rowGap: 24,
  },
  actionWrapper: {
    flexDirection: "row",
    columnGap: 18,
  },
  count: {
    fontSize: 24,
    fontWeight: "bold",
  },
});
style.ts – src/components/Counter
import {StyleSheet} from "react-native";

export const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
    rowGap: 24,
  },
  actionWrapper: {
    flexDirection: "row",
    columnGap: 18,
  },
  count: {
    fontSize: 24,
    fontWeight: "bold",
  },
});

After copying the styles, we need to export the styles so we can use it in index.tsx

index.tsx – src/components/Counter
import React from "react";
import {Button, Text, View} from "react-native";
import {styles} from "./styles";

const Counter: React.FC = (): React.JSX.Element => {
  return (
    <View style={styles.container}>
      <Text style={styles.count}>{count}</Text>
      <View style={styles.actionWrapper}>
        <Button title="Decrease" onPress={decrement} disabled={count === 0} />
        <Button title="Increase" onPress={increment} />
      </View>
      <Button title="Reset" onPress={reset} disabled={count === 0} />
    </View>
  )
}

export default Counter;

The remaining issue with the component is handling the values that need to be passed from the parent component as properties, also known as props. Let’s create an interface for that.

index.tsx – src/components/Counter
import React from "react";
import {Button, Text, View} from "react-native";
import {styles} from "./styles";

interface CounterProps {
  count: number;
  decrement: () => void;
  increment: () => void;
  reset: () => void;
}

const Counter: React.FC<CounterProps> = ({count, decrement, increment, reset}): React.JSX.Element => {
  return (
    <View style={styles.container}>
      <Text style={styles.count}>{count}</Text>
      <View style={styles.actionWrapper}>
        <Button title="Decrease" onPress={decrement} disabled={count === 0}/>
        <Button title="Increase" onPress={increment}/>
      </View>
      <Button title="Reset" onPress={reset} disabled={count === 0}/>
    </View>
  )
}

export default Counter;

An interface serves as a blueprint for the properties of a component. To link an interface to a component, we use the generic React.FC<T>, where T represents the interface or type of the component, as shown on line 12. Now, let’s update our App.tsx to include the Counter component.

App.tsx
import {StatusBar} from "expo-status-bar";
import {useState} from "react";
import {StyleSheet, View} from "react-native";
import Counter from "./src/components/Counter";

export default function App() {
  const [count, setCount] = useState(0);

  function increment() {
    setCount((prevState) => prevState + 1);
  }

  function decrement() {
    setCount((prevState) => prevState - 1);
  }

  function reset() {
    setCount(0);
  }

  return (
    <View style={styles.container}>
      <Counter count={count} decrement={decrement} increment={increment} reset={reset}/>
      <StatusBar style="auto"/>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

We imported our Counter from the components folder and replaced all instances of the Counter components with this single Counter component. The component’s properties have been passed appropriately.

  • The counter value is derived from the state created in the App.tsx component.
  • The increment, decrement, and reset functions are passed as values to their respective props from the parent App.tsx component.

To test if the counter functions as expected, run the application and try increasing, decreasing, and resetting the values. Everything should work fine so far, but how can we make it reusable? Simply copy and paste the Counter component below, as demonstrated.

App.tsx
import {StatusBar} from "expo-status-bar";
import {useState} from "react";
import {StyleSheet, View} from "react-native";
import Counter from "./src/components/Counter";

export default function App() {
  const [count, setCount] = useState(0);

  function increment() {
    setCount((prevState) => prevState + 1);
  }

  function decrement() {
    setCount((prevState) => prevState - 1);
  }

  function reset() {
    setCount(0);
  }

  return (
    <View style={styles.container}>
      <Counter count={count} decrement={decrement} increment={increment} reset={reset}/>
      <Counter count={count} decrement={decrement} increment={increment} reset={reset}/>
      <StatusBar style="auto"/>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

Awesome! We have created 2 counters with a single component but there is still another issue. Can you guess? Yes! you might have guessed it right. If you click the increase button, you will see that both the counters increment the counter at a single time. The problem is as follows

We need to isolate the Counter component by giving it its own counter state, along with the increment, decrement, and reset functions. Since the components are now reusable, we can move the counter state, increment, decrement, and reset functions directly into the Counter component.

TSX
import React, {useState} from "react";
import {Button, Text, View} from "react-native";
import {styles} from "./styles";

const Counter: React.FC = (): React.JSX.Element => {
  const [count, setCount] = useState(0);

  function increment() {
    setCount((prevState) => prevState + 1);
  }

  function decrement() {
    setCount((prevState) => prevState - 1);
  }

  function reset() {
    setCount(0);
  }

  return (
    <View style={styles.container}>
      <Text style={styles.count}>{count}</Text>
      <View style={styles.actionWrapper}>
        <Button title="Decrease" onPress={decrement} disabled={count === 0}/>
        <Button title="Increase" onPress={increment}/>
      </View>
      <Button title="Reset" onPress={reset} disabled={count === 0}/>
    </View>
  )
}

export default Counter;

Now, let’s update App.tsx

App.tsx
import {StatusBar} from "expo-status-bar";
import {StyleSheet, View} from "react-native";
import Counter from "./src/components/Counter";

export default function App() {
  return (
    <View style={styles.container}>
      <Counter/>
      <Counter/>
      <StatusBar style="auto"/>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

As you can see, isolating the Counter component along with its functionality has significantly minimized the code.

Congratulations 🎉 you’ve just created your first reusable component! This approach, where components are isolated for reusability, is a common practice among developers. It’s not limited to just React Native or React. You can access the completed project at the following link

https://github.com/devnur-org/rn-expo-counter/tree/reusable-components


Share with your audience and help them grow too!

About Author

Arslan Mushtaq

A skilled software developer with 10+ years of experience in mobile and web development, proficient in technologies such as React Native, Native Android, and iOS for mobile, and React.js and Next.js for web development. Additional expertise in backend technologies including Node.js, Express.js, Amazon Web Services, and Google Firebase, facilitating collaboration with cross-functional teams across various domains.