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.
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.
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