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.
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
Add Nativewind and Tailwind CSS as the project's dependencies.
yarn add nativewind
yarn add --dev tailwindcss
π‘ 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: [],
};
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"],
};
};
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='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;
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>
);
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>
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>;
}
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}`;
}
};
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);
};
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);
}
}
}
};
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;
}
};
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);
}
};
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));
};
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(-</span><span class="p">${</span><span class="nx">secondNumber</span><span class="p">}</span><span class="s2">
);
} else {
if (firstNumber.toString().startsWith("-")) {
return setFirstNumber(firstNumber.toString().replace("-", "+"));
}
if (firstNumber.toString().startsWith("+")) {
return setFirstNumber(firstNumber.toString().replace("+", "-"));
}
return setFirstNumber(-</span><span class="p">${</span><span class="nx">firstNumber</span><span class="p">}</span><span class="s2">
);
}
};
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
Top comments (2)
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!
Awesome David!
Come to DIscord :)