DEV Community

Cover image for Building a calculator app with React Native and Tailwind CSS
David Asaolu
David Asaolu

Posted on • Edited on

Building a calculator app with React Native and Tailwind CSS

Recently, I completed a React Native course, and here I am writing about my first project - a calculator app. 😎

I decided to start with small projects before going into complex mobile applications. So, expect more tutorials about building advanced or complex mobile applications from me.

I've got this!

Therefore, in this tutorial, I'll walk you through how to build a mobile calculator app with React Native and Tailwind CSS.

πŸ’‘PS: This tutorial assumes you've React Native and an emulator (or device) installed on your computer.

What is React Native?

React Native is an open-source React framework that enables you to create native applications for both IOS and Android with JavaScript code. Although in this tutorial, we'll build the application with Expo.

Expo saves us from the complex configurations required to create a native application with the React Native CLI, making it the easiest and fastest way to build and publish React Native apps.

πŸ’‘ Dear prospective employers! I'm actively seeking remote opportunities as a Junior Frontend Engineer (Intern) or Technical Writer where I can thrive, learn, and grow. Here is my portfolio and resume.

Project Set up

Create a new Expo application by running the code snippet below.

npx create-expo-app calculator-app
Enter fullscreen mode Exit fullscreen mode

Add Nativewind and Tailwind CSS as the project's dependencies.

yarn add nativewind
yarn add --dev tailwindcss
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ NativeWind uses Tailwind CSS as its scripting language, therefore enabling us to write the same styles with Tailwind CSS for both Android and iOS React Native apps.

Run npx tailwindcss init to create a tailwind.config.js file and update the tailwind.config.js file as done below.

module.exports = {
    content: [
        "./App.{js,jsx,ts,tsx}",
        "./<custom directory>/**/*.{js,jsx,ts,tsx}",
    ],
    theme: {
        extend: {},
    },
    plugins: [],
};
Enter fullscreen mode Exit fullscreen mode

Finally, add the Babel plugin to the babel.config.js.

// babel.config.js
module.exports = function (api) {
    api.cache(true);
    return {
        presets: ["babel-preset-expo"],
        plugins: ["nativewind/babel"],
    };
};
Enter fullscreen mode Exit fullscreen mode

Congratulations!πŸŽ‰ You've successfully configured Tailwind CSS. You can now style the application with Tailwind CSS.

Building the application user interface

In this section, you'll learn how to build the user interface for the application.

Application UI interface

From the image above, we have a large display and a group of buttons. Next, create a component that returns a row of buttons containing numbers and an operation.

Button Row

import { Pressable, Text, View } from "react-native";
import React from "react";

const ButtonGroup = ({
    first,
    second,
    third,
    fourth,
    handleNumberPress,
    handleOperationPress,
}) => {
    return (
        <View className='flex-row items-center w-full space-x-3 justify-center px-10 mb-2'>
            <Pressable
                className=' bg-white py-4 rounded-xl shadow-md w-1/4'
                onPress={() => handleNumberPress(first)}
            >
                <Text className='text-3xl text-gray-600 font-semibold text-center'>
                    {first}
                </Text>
            </Pressable>
            <Pressable
                className=' bg-white py-4   rounded-xl shadow-md w-1/4'
                onPress={() => handleNumberPress(second)}
            >
                <Text className='text-3xl text-gray-600 font-semibold text-center'>
                    {second}
                </Text>
            </Pressable>
            <Pressable
                className=' bg-white py-4 rounded-xl shadow-md w-1/4'
                onPress={() => handleNumberPress(third)}
            >
                <Text className='text-3xl text-gray-600 font-semibold text-center'>
                    {third}
                </Text>
            </Pressable>
            <Pressable
                className='bg-blue-600 py-4  rounded-xl shadow-md w-1/4'
                onPress={() => handleOperationPress(fourth)}
            >
                <Text className='text-3xl text-white font-semibold text-center'>
                    {fourth}
                </Text>
            </Pressable>
        </View>
    );
};

export default ButtonGroup;
Enter fullscreen mode Exit fullscreen mode

The code snippet above accepts each value of the button and the function to be executed when you press the buttons. The last button within the component will contain an operation; that's why there is another function - handleOperationPress for its action.

Next, update the App.js file to render the UI component below.

return (
    <SafeAreaView className='flex-1 items-center'>
        <View className='flex-1 w-full bg-blue-50 rounded-xl p-4 mb-4 items-end justify-end'>
            <Text className={`${firstNumber.length <= 7 ? "text-8xl" : "text-6xl"}`}>
                {display()}
            </Text>
        </View>

        <View className='w-full rounded-xl py-4'>
            {/* --- button container ---*/}
        </View>
    </SafeAreaView>
);
Enter fullscreen mode Exit fullscreen mode

The code snippet above renders the calculator's display screen.

Render the buttons using the code snippet below.

<View className='w-full rounded-xl py-4'>
    <View className='flex-row items-center w-full space-x-3 justify-center px-10 mb-2'>
        <Pressable
            className='bg-gray-600 py-4   rounded-xl shadow-md w-1/4'
            onPress={() => clearScreen()}
        >
            <Text className='text-3xl text-white font-semibold text-center'>C</Text>
        </Pressable>
        <Pressable
            className='bg-gray-600 py-4   rounded-xl shadow-md w-1/4'
            onPress={() => changeSignFunction()}
        >
            <Text className='text-3xl text-white font-semibold text-center'>+/-</Text>
        </Pressable>
        <Pressable
            className='bg-gray-600 py-4 rounded-xl shadow-md w-1/4'
            onPress={() => percentageFunction()}
        >
            <Text className='text-3xl text-white font-semibold text-center'>%</Text>
        </Pressable>
        <Pressable
            className='bg-blue-600 py-4   rounded-xl shadow-md w-1/4'
            onPress={() => handleOperationPress("Γ·")}
        >
            <Text className='text-3xl text-white font-semibold text-center'>Γ·</Text>
        </Pressable>
    </View>
    <ButtonGroup
        first='7'
        second='8'
        third='9'
        fourth='x'
        handleNumberPress={handleNumberPress}
        handleOperationPress={handleOperationPress}
    />
    <ButtonGroup
        first='4'
        second='5'
        third='6'
        fourth='-'
        handleNumberPress={handleNumberPress}
        handleOperationPress={handleOperationPress}
    />
    <ButtonGroup
        first='1'
        second='2'
        third='3'
        fourth='+'
        handleNumberPress={handleNumberPress}
        handleOperationPress={handleOperationPress}
    />
    <View className='flex-row items-center w-full space-x-3 justify-center px-10 mb-2'>
        <Pressable
            className='bg-white py-4   rounded-xl shadow-md w-1/4'
            onPress={() => handleNumberPress(".")}
        >
            <Text className='text-3xl text-gray-600 font-semibold text-center'>
                .
            </Text>
        </Pressable>
        <Pressable
            className='py-4   rounded-xl shadow-md w-1/4'
            onPress={() => handleNumberPress("0")}
        >
            <Text className='text-3xl text-gray-600 font-semibold text-center'>
                0
            </Text>
        </Pressable>
        <Pressable
            className='bg-white py-4 rounded-xl items-center justify-center shadow-md w-1/4'
            onPress={() => deleteFunction()}
        >
            <Feather name='delete' size={24} color='black' />
        </Pressable>
        <Pressable
            className='bg-blue-600 py-4 rounded-xl shadow-md w-1/4'
            onPress={() => getResult()}
        >
            <Text className='text-3xl text-white font-semibold text-center'>=</Text>
        </Pressable>
    </View>
</View>
Enter fullscreen mode Exit fullscreen mode

The code snippet renders the buttons on the screen. I didn't use the ButtonGroup component for the top and bottom row because its functions and colours are different from others.

Creating the button functionalities

First, you need to create three different states that hold the button values and the operation.

import { StatusBar } from "expo-status-bar";
import { Pressable, SafeAreaView, Text, View } from "react-native";
import ButtonGroup from "./components/ButtonGroup";
import { Feather } from "@expo/vector-icons";
import { useState } from "react";

export default function App() {
    const [firstNumber, setFirstNumber] = useState("");
    const [secondNumber, setSecondNumber] = useState("");
    const [operation, setOperation] = useState("");

    return <div>{/* --- UI components ---*/}</div>;
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above shows that the user can input at least two numbers into the calculator and an operation, except for the percent operation.

Create a display function that shows the user's input on the screen.

const display = () => {
    if (!secondNumber && !firstNumber) {
        return "0";
    }
    if (!secondNumber) {
        return `${firstNumber}${operation}`;
    } else {
        return `${secondNumber}`;
    }
};
Enter fullscreen mode Exit fullscreen mode

The function above checks if the user has not entered a number, then returns "0"; otherwise it returns the right number on the screen.

Add a function that runs when a user clicks on the operation buttons.

const handleOperationPress = (value) => {
    if (
        firstNumber[firstNumber.length - 1] === "x" ||
        firstNumber[firstNumber.length - 1] === "+" ||
        firstNumber[firstNumber.length - 1] === "-" ||
        firstNumber[firstNumber.length - 1] === "%" ||
        firstNumber[firstNumber.length - 1] === "Γ·" ||
        operation !== ""
    ) {
        return;
    }
    setOperation(value);
};
Enter fullscreen mode Exit fullscreen mode

The function above checks if the last input is not an operation before updating the operation state.

Create another function that is executed every time the user presses a number.

const handleNumberPress = (value) => {
    if (!operation && firstNumber.length < 10) {
        if (value !== ".") {
            setFirstNumber(firstNumber + value);
        } else {
            if (firstNumber.includes(".")) {
                return;
            } else {
                setFirstNumber(firstNumber + value);
            }
        }
    }
    if (operation && secondNumber.length < 10) {
        if (value !== ".") {
            setSecondNumber(secondNumber + value);
        } else {
            if (secondNumber.includes(".")) {
                return;
            } else {
                setSecondNumber(secondNumber + value);
            }
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

The function above checks if the user has entered an operation before setting the firstNumber and secondNumber state values.
When a user press a number button, the calculator sets its value to the firstNumber variable. Any subsequent number after an operation sign is set as the secondNumber.

Create the equals-to function as shown below.

const getResult = () => {
    switch (operation) {
        case "+":
            clearScreen();
            setFirstNumber(parseFloat(firstNumber) + parseFloat(secondNumber));
            setOperation("");
            setSecondNumber("");
            break;
        case "-":
            clearScreen();
            setFirstNumber(parseFloat(firstNumber) - parseFloat(secondNumber));
            setOperation("");
            setSecondNumber("");
            break;
        case "x":
            clearScreen();
            setFirstNumber(parseFloat(firstNumber) * parseFloat(secondNumber));
            setOperation("");
            setSecondNumber("");
            break;
        case "Γ·":
            clearScreen();
            const value = parseInt(firstNumber) / parseInt(secondNumber);
            if (value !== Math.round(value) && value !== Math.trunc(value)) {
                setFirstNumber(value.toFixed(5));
            } else {
                setFirstNumber(value);
            }
            setOperation("");
            setSecondNumber("");
            break;
        default:
            clearScreen();
            break;
    }
};
Enter fullscreen mode Exit fullscreen mode

The function above accepts the values of the firstNumber and the secondNumber states and performs the right operation on the values.

When a user clicks on the percentage function, it returns the value in percentage.

const percentageFunction = () => {
    if (!secondNumber) {
        return setFirstNumber(parseFloat(firstNumber) / 100);
    }
};
Enter fullscreen mode Exit fullscreen mode

Add a function that enables users to clear the screen or remove the recently entered value.

//πŸ‘‡πŸ» clears the screen
const clearScreen = () => {
    setFirstNumber("");
    setSecondNumber("");
    setOperation("");
};

//πŸ‘‡πŸ» removes the recently entered value
const deleteFunction = () => {
    if (operation) {
        return setSecondNumber(secondNumber.slice(0, -1));
    }
    return setFirstNumber(firstNumber.toString().slice(0, -1));
};
Enter fullscreen mode Exit fullscreen mode

Finally, create the changeSignFunction function to enable users to enter negative and positive values into the calculator. The function checks the sign on a number and toggles it.

const changeSignFunction = () => {
    if (operation) {
        if (secondNumber.startsWith("-")) {
            return setSecondNumber(secondNumber.replace("-", "+"));
        }
        if (secondNumber.startsWith("+")) {
            return setSecondNumber(secondNumber.replace("+", "-"));
        }
        return setSecondNumber(`-${secondNumber}`);
    } else {
        if (firstNumber.toString().startsWith("-")) {
            return setFirstNumber(firstNumber.toString().replace("-", "+"));
        }
        if (firstNumber.toString().startsWith("+")) {
            return setFirstNumber(firstNumber.toString().replace("+", "-"));
        }
        return setFirstNumber(`-${firstNumber}`);
    }
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

As I stated earlier, this is a beginner React Native project; hopefully, it helps you get started with React Native or build your first mobile application.

You can get the source code on GitHub and try out a demo of the project here.

Thank you for reading! πŸŽ‰

Open to workπŸ™‚

Did you enjoy this article or need an experienced Technical Writer / React Developer for a remote, full-time, or contract-based role? Feel free to contact me.
GitHub || LinkedIn || Twitter

Buy David a coffee
Thank You

Top comments (2)

Collapse
 
efpage profile image
Eckehard

Really impressive! React is really a brain drainer....

You can solve the same task without react in just 61 lines of code, all batteries included!

Collapse
 
nevodavid profile image
Nevo David

Awesome David!
Come to DIscord :)