Geek Culture
Published in

Geek Culture

Stopwatch Application Using React Native Expo

Make a stopwatch app for Android and iOS using react native expo

Stopwatch app using react native expo

Hello, react-native developers..!!

My name is Rohit Kumar Thakur. I usually talk about react-native, Django, Data-science, Machine Learning, and Python. In this article, I am going to talk about a react native expo project. From the heading, you already know that we are going to make a stopwatch using some javascript code. So, take a cup of coffee and starts this project.

Here is the step-by-step video tutorial of this article:

Project setup and Installation

  • Choose a directory of your choice and start the expo project using the following command:
expo init stopwatch
  • Select the blank templates and continue to dependencies downloading. We are selecting blank templates because we are building everything from basics
  • Navigation to the newly built-in directory using the command: cd stopwatch
  • Install the following javascript library:
npm install react-native-paper
npm install expo-constants

We are done with the project set-up and installation here. Now, open this project in your favorite text editor, VS Code. XD :)

The final project output is going to look something like this:

Stopwatch using react native expo

Code

Now, if you look at the above image, you can see the components out there. First, we have the header components, then we have components of the timer, then we have two control buttons, and the last we have a lap view. This lap view is scrollable, you can scroll and see the lap time of things that you are measuring using the stopwatch.

We will write the code of each component one by one and join them together for a perfect view. Make a separate directory inside the stopwatch project directory and name it as “components”. We will start with the header component first. Inside the components directory, make a javascript file and name it as “Header.js”.

Header.js

import * as React from 'react';
import { Appbar } from 'react-native-paper';
// Heading of the appconst MyHeader = () => {return (
<Appbar.Header style={{backgroundColor:'black'}}>
<Appbar.Content title="Stopwatch" style={{ alignItems: 'center'}}/>
</Appbar.Header>
);
};
export default MyHeader;

This is a simple header using react native paper library. The background color of the header is set to black and the title of the header is aligned to the center.

util.js

const padToTwo = (number) => (number <= 9 ? `0${number}` : number);export const displayTime = (centiseconds) => {
let minutes = 0;
let seconds = 0;
if (centiseconds < 0) {
centiseconds = 0;
}
if (centiseconds < 100) {
return `00:00:${padToTwo(centiseconds)}`;
}
let remainCentiseconds = centiseconds % 100;
seconds = (centiseconds - remainCentiseconds) / 100;
if (seconds < 60) {
return `00:${padToTwo(seconds)}:${padToTwo(remainCentiseconds)}`;
}
let remainSeconds = seconds % 60;
minutes = (seconds - remainSeconds) / 60;
return `${padToTwo(minutes)}:${padToTwo(remainSeconds)}:${padToTwo(remainCentiseconds)}`;
};

Make the util.js file inside the components directory. And add the above code to it.

First, I added an arrow function padToTwo and pass the props number to it. This function checks the numbers. If the number is less than 9 then it returns the number with 0 on the left side otherwise returns the number itself. For example, 7 is returned as 07, and 17 is returned as 17.

Next, we have to deal with the time. We all know that 1 second has 100 centiseconds. We are going to start with the centiseconds. First, assign the value to seconds and minutes to 0. If the centiseconds value is less than 100 then return the centiseconds according to the padToTwo function.

How to deal with seconds now? You don’t have to worry about that. Centiseconds value will take care of that. For example, If the value of centiseconds will be 105.

let remainCentiseconds = centiseconds % 100;  
// 105 % 100 = 5
// remaiCentiseconds = 5
seconds = (centiseconds - remainCentiseconds) / 100;
// (105-5)/100 = 1
// seconds = 1
if (seconds < 60) {
return `00:${padToTwo(seconds)}:${padToTwo(remainCentiseconds)}`;
}
// here the seconds is 1, which is less than 60. So the output will be
// 00:01:05 (stopwatch)

I hope now you can understand what is going on in the display time function. The same logic goes for displaying minutes too. For minutes we have to play the seconds.

The next is the buttons. So, add a file control.js inside the components directory

Control.js

import React from "react";
import { StyleSheet, Text, View, TouchableOpacity } from "react-native";
// we will deal with buttons herefunction Control({ isRunning, handleLeftButtonPress, handleRightButtonPress }) {
return (
<>
<TouchableOpacity
style={[
styles.controlButtonBorder,
{ backgroundColor: isRunning ? "#333333" : "#1c1c1e" },
]}
onPress={handleLeftButtonPress}
>
<View style={styles.controlButton}>
<Text style={{ color: isRunning ? "#fff" : "#9d9ca2" }}>
{isRunning ? "Lap" : "Reset"}
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.controlButtonBorder,
{ backgroundColor: isRunning ? "#340e0d" : "#0a2a12" },
]}
onPress={handleRightButtonPress}
>
<View style={styles.controlButton}>
<Text style={{ color: isRunning ? "#ea4c49" : "#37d05c" }}>
{isRunning ? "Stop" : "Start"}
</Text>
</View>
</TouchableOpacity>
</>
);
}
const CENTER = {
justifyContent: "center",
alignItems: "center",
};
const styles = StyleSheet.create({
controlButtonBorder: {
...CENTER,
width: 70,
height: 70,
borderRadius: 70,
},
controlButton: {
...CENTER,
width: 65,
height: 65,
borderRadius: 65,
borderColor: "#000",
borderWidth: 1,
},
});
export default React.memo(Control);

In this file, we take care of the two control buttons. The props isRunning, handleLeftButtonPress, handleRightButtonPress will be handled later.

React.memo is a higher-order component. If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result. Means, React.memo checks to see if an upcoming render will be different than the previous render. If they are the same, then it keeps the previous one.

Result.js

import React from "react";
import { StyleSheet, Text, ScrollView, View } from "react-native";
import { displayTime } from "./util";
// print the lap timefunction Result({ results }) {

return (
<ScrollView>
<View style={styles.resultItem} />
{results.map((item, index) => (
<View key={index} style={styles.resultItem}>
<Text style={styles.resultItemText}>
Lap {results.length - index}
</Text>
<Text style={styles.resultItemText}>{displayTime(item)}</Text>
</View>
))}
</ScrollView>
);
}
const styles = StyleSheet.create({
resultItem: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
borderBottomWidth: 1,
borderColor: "#313131",
height: 50,
paddingHorizontal: 15,
},
resultItemText: { color: "#fff" },
});
export default React.memo(Result);

Stopwatch.js

In this javascript file, we are going to use all the components we have created so far.

import  React, { useState, useRef, useCallback } from "react";
import { StyleSheet, SafeAreaView, Text, View, Platform } from "react-native";
import { StatusBar } from "expo-status-bar";
import Constants from "expo-constants";
import Result from "./Result";
import Control from "./Control";
import { displayTime } from "./util";
import MyHeader from "./Header";
export default function StopWatch() {
const [time, setTime] = useState(0);
const [isRunning, setRunning] = useState(false);
const [results, setResults] = useState([]);
const timer = useRef(null);
const handleLeftButtonPress = useCallback(() => {
if (isRunning) {
setResults((previousResults) => [time, ...previousResults]);
} else {
setResults([]);
setTime(0);
}
}, [isRunning, time]);
const handleRightButtonPress = useCallback(() => {
if (!isRunning) {
const interval = setInterval(() => {
setTime((previousTime) => previousTime + 1);
}, 10);
timer.current = interval;
} else {
clearInterval(timer.current);
}
setRunning((previousState) => !previousState);
}, [isRunning]);
return (
<SafeAreaView style={styles.container}>
<MyHeader/>
<StatusBar style="light" />
<View style={styles.display}>
<Text style={styles.displayText}>{displayTime(time)}</Text>
</View>
<View style={styles.control}>
<Control
isRunning={isRunning}
handleLeftButtonPress={handleLeftButtonPress}
handleRightButtonPress={handleRightButtonPress}
/>
</View>
<View style={styles.result}>
<Result results={results} />
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "black",
paddingTop: Constants.statusBarHeight,
},
display: {
flex: 3 / 5,
justifyContent: "center",
alignItems: "center",
},
displayText: {
color: "#fff",
fontSize: 70,
fontWeight: "200",
fontFamily: Platform.OS === "ios" ? "Helvetica Neue" : null,
},
control: {
height: 70,
flexDirection: "row",
justifyContent: "space-around",
},
result: { flex: 2 / 5 },
});
  • First, we imported the required libraries
  • Then we added the useState hook for results, time, and isRunning.
  • Code for left-hand side button. with the help of this button, we set the lap and reset the stopwatch.
  • Next, code to handle the right-hand side button.
  • In both the control button we used useCallback react hook. The useCallback hook is used when you have a component in which the child is rerendering again and again without need. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
  • At last, render the component accordingly.

App.js

import { StyleSheet, View } from 'react-native';
import StopWatch from './components/StopWatch';
export default function App() {
return (
<View style={styles.container}>
<StopWatch/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

Now, run this react native expo application using the following command:

npm start

Scan the QR code using your mobile device and see the project in action.

The Github code of this project is here

Now, clap.. clap..clap, and do follow for more project-based articles on react native expo.

Thanks for reading.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store