Skip to main content
Photo from unsplash: tomas-yates-TQHDStvFi60-unsplash_dvaxkx

Building a calculator app with React Native and Tailwind CSS

Written on July 21, 2023 by Developer Dennis.

7 min read
โ€“โ€“โ€“ views

Introduction

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.

Light_Theme_lgjh8k

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.

Project Set up

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

npx create-expo-app calculator-app
tsx

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

yarn add nativewind yarn add --dev tailwindcss
tsx

๐Ÿ’ก 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: [], };
tsx

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'], }; };
tsx

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.

Light_Theme_lgjh8k

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.

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

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='mb-4 w-full flex-1 items-end justify-end rounded-xl bg-blue-50 p-4'> <Text className={`${firstNumber.length <= 7 ? 'text-8xl' : 'text-6xl'}`}> {display()} </Text> </View> <View className='w-full rounded-xl py-4'> {/* --- button container ---*/} </View> </SafeAreaView> );
tsx

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='mb-2 w-full flex-row items-center justify-center space-x-3 px-10'> <Pressable className='w-1/4 rounded-xl bg-gray-600 py-4 shadow-md' onPress={() => clearScreen()} > <Text className='text-center text-3xl font-semibold text-white'>C</Text> </Pressable> <Pressable className='w-1/4 rounded-xl bg-gray-600 py-4 shadow-md' onPress={() => changeSignFunction()} > <Text className='text-center text-3xl font-semibold text-white'>+/-</Text> </Pressable> <Pressable className='w-1/4 rounded-xl bg-gray-600 py-4 shadow-md' onPress={() => percentageFunction()} > <Text className='text-center text-3xl font-semibold text-white'>%</Text> </Pressable> <Pressable className='w-1/4 rounded-xl bg-blue-600 py-4 shadow-md' onPress={() => handleOperationPress('รท')} > <Text className='text-center text-3xl font-semibold text-white'>รท</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='mb-2 w-full flex-row items-center justify-center space-x-3 px-10'> <Pressable className='w-1/4 rounded-xl bg-white py-4 shadow-md' onPress={() => handleNumberPress('.')} > <Text className='text-center text-3xl font-semibold text-gray-600'> . </Text> </Pressable> <Pressable className='w-1/4 rounded-xl py-4 shadow-md' onPress={() => handleNumberPress('0')} > <Text className='text-center text-3xl font-semibold text-gray-600'> 0 </Text> </Pressable> <Pressable className='w-1/4 items-center justify-center rounded-xl bg-white py-4 shadow-md' onPress={() => deleteFunction()} > <Feather name='delete' size={24} color='black' /> </Pressable> <Pressable className='w-1/4 rounded-xl bg-blue-600 py-4 shadow-md' onPress={() => getResult()} > <Text className='text-center text-3xl font-semibold text-white'>=</Text> </Pressable> </View> </View>
tsx

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

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}`; } };
tsx

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

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

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

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

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

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}`); } };
tsx
Tweet this article

Enjoying this post?

Don't miss out ๐Ÿ˜‰. Get an email whenever I post, no spam.

Subscribe Now