Build a Counter App in React Native

Build a Counter App in React Native

New to React Native? No issues, you’re at the right place. Today we jump into developing a very simple application called Counter where you can increment and decrease a number. Ready for it? Let’s dive in.

Understanding the project

A Counter is going to be a simple counter with 3 view elements within in including the following:

  1. Number (Text Element) – Which will be Incremented or decremented
  2. Increment Button – This will be used to increment the text element containing the number.
  3. Decrement Button – This will be used to decrement the text element containing the number.
  4. Reset Button – This will be used to reset the number to 0.

Let’s keep the user interface as simple as possible because we want to create our first application and understand how React Native works. The user interface will look like this:

Setting up the Development Environment

Before diving into React Native development, it’s essential to configure your development environment. You can follow the official React Native guide for setting it up:

https://reactnative.dev/docs/environment-setup

If you’re using Expo as your framework, you can run the project on both Android and iOS platforms. However, iOS requires either a physical device or a macOS to run on a simulator. On Windows, you can still run the Expo app on an Android emulator by setting up the development environment accordingly.

Creating the counter project

Let’s start with creating a new Expo React Native project. Fire up the terminal and enter the following command to create a new Expo project.

npx create-expo-app@latest --template blank-typescript

We are choosing the blank-typescript template which will remove a lot of unnecessary boilerplate code and make it easier and clearer to work with. Expo CLI will ask for the app name, input counter, and press Enter

What is your app named? › counter

After pressing enter, Expo CLI will start generating files and installing dependencies. After installing all dependencies the process will end by saying ✅ Your project is ready! Now, let’s run the project.

Navigate to the project directory in a Terminal and enter the following command

npm start

To run the application on an Android Emulator, press 'a', and to run it on an iOS Simulator press 'i'. The Android emulator and iOS simulator will launch automatically if your development environment is set up correctly.

Open the project in your favorite code editor, whereas the most recommended code editor in the market is Visual Studio Code. After opening the project, tap on App.tsx. Remove the boilerplate code within the file and you will end up with the following:

import { StatusBar } from "expo-status-bar";
import { StyleSheet, View } from "react-native";

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

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

Adding Elements

Let’s add the elements mentioned above to create the skeleton of the counter. The first element to add is Number (Text Element) which will act as the counter.

Adding the count text element

To display text in React Native, we can use the Text component from the react-native library. The initial value of the number is going to be 0.

import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";

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

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

Adding buttons

For the second, third, and fourth elements, we will be using another component called Button from the react-native library. We will be utilizing the title prop to enter the name for each action.

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

export default function App() {
  return (
    <View style={styles.container}>
      <Text>0</Text>
      <Button title="Decrease" />
      <Button title="Increase" />
      <Button title="Reset" />
      <StatusBar style="auto" />
    </View>
  );
}

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

The result above is not the expected user interface, so let’s add the necessary styles for not mixing up interactions. We will be updating the design as follows:

  • The Increment and Decrement buttons should be aligned in a horizontal row next to each other with a spacing of 18dip between them.
  • The Count Text should be bold and have a font size of 24dip.
  • The spacing between the elements should be 24dip

The updates in the code will be as follows:

  • To add increment and decrement buttons within a single row, we will be nesting both the buttons in a View Component.
  • To apply the styles to any component we will be using the style prop.
import { StatusBar } from "expo-status-bar";
import { Button, StyleSheet, Text, View } from "react-native";

export default function App() {
  return (
    <View style={styles.container}>
      <Text style={styles.count}>0</Text>
      <View style={styles.actionWrapper}>
        <Button title="Decrease" />
        <Button title="Increase" />
      </View>
      <Button title="Reset" />
      <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",
  },
});

Adding functions for each button

As we click on any button, nothing happens but for the counter to be increased, decreased, or reset we need the value of the text to increase when the Increase button is pressed, decrease when the Decrease button is pressed, and reset when the Reset button is pressed. Following questions should popup in your mind:

  1. How will the count text increase, decrease, or reset?
  2. How will any of the buttons know the value of the count text?

To answer these questions, we will be following these steps:

  1. We will utilize useState, a hook that keeps track of the state (values) stored within it. We will set the initial value to 0 and add it to the Text component children.
  2. We will create the increment function which will increase the count value by 1 and attach it to the onPress prop of the Increase button for it to function.
  3. We will create the decrement function which will decrease the count value by 1 and attach it to the onPress prop of the Decrement button for it to function.
  4. We will create the reset function which will reset the count to 0 and attach it to the onPress prop of the Reset button for it to function.

Utilizing useState

Let’s start by using the useState hook for the count text. It can be imported from the react library. The useState hook returns and array of two values out of which the first is the value and the second is the setter.

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);

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

Adding increment action

The increment function will increment the number of counts by 1. Let’s add a function called increment within the component, including the logic for incrementing the count variable. At last, we attach the function to the onPress prop to the Increment Button.

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);
  }

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


In line 9, the callback parameter is utilized to access the previous state (prevState), which holds the current value. We then increment this value by 1 and return the result. The increment function is the short version of the following:

setCount((prevState) => {
  return prevState + 1;
});

Running the application, and pressing the increment button will increase the count.

Adding decrement action

The decrement function will decrease the number of counts by 1. Let’s add a function called decrement within the component, including the logic for decrementing the count variable. At last, we attach the function to the onPress prop to the Decrement Button.

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);
  }

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

Running the application, and pressing the decrement button will decrease the count.

Adding the reset action

The reset function will reset the number of counts to 0. Let’s add a function called reset within the component, including the logic for resetting the count variable. Then, we attach the function to the onPress prop to the Reset Button.

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} />
        <Button title="Increase" onPress={increment} />
      </View>
      <Button title="Reset" onPress={reset} />
      <StatusBar style="auto" />
    </View>
  );
}

Run the application, increment or decrement the count, and pressing the reset button will bring the count back to 0.

Now, we have a running counter that fulfills the requirements of a counter. The last step towards making the counter more realistic is to add the following validations:

  1. The counter should not decrease less than 0
  2. The reset and decrement button should be disabled when counter value is 0

Limiting the count to 0

To limit the counter to 0, we can disable the Decrement button using the disabled prop when the count value reaches 0

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} />
      <StatusBar style="auto" />
    </View>
  );
}

Disabling the Reset button

To disable the reset button, again we will use the disabled prop but now for the Reset Button.

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>
  );
}
Reset Button disabled on Android and iOS

Congratulations 🎉 ! You have built your first counter application on both Android and iOS. Great work, keep it up. So, what’s next? What if you need more than two counters? Will you repeat the process? Nah, we will utilize something called Reusable Components. Learn more about it below.


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.