Hello Everyone, I'm back after 1 year and 4 months.This time with react native project.
Let's start the project
I created this project with Expo and used used Expo Router for routing.
Create a new folder and open the terminal and run this command
npx create-expo-app@latest
After running this command successfully, You can remove the boilerplate code and start fresh with a new project. Run the following command to reset your project:
npm run reset-project
Before jumping to the code, let's understand the functionality of our app.
When the user is on home screen then the user has to choose one mathematical operation that they want to practice.
Once they select the operation then they are moved to the Quiz Screen and questions will start appearing on the user screen. The user have to answer the question within 10sec and if the answer is correct then the score is increased by 1 and the next question will appear and if the user does not answer the question within 10sec then the next question will be rendered.
app
is the starting point of our application and inside app
, _layout.tsx
is our root layout and index.tsx
is our home page.
_layout.tsx :-
import { Stack } from "expo-router";
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{headerShown:false}}/>
<Stack.Screen name='quiz/[id]'options={{headerShown:false}}/>
</Stack>
);
}
Now we have two screens, one the home screen and the other will be a dynamic screen which will render the questions based on the operation selected by the user.
index.tsx :-
// HomeScreen.js
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import {router} from 'expo-router'
const HomeScreen = () => {
const handleStartQuiz = (operation: string) => {
router.push({pathname:'/quiz/[id]',
params:{id:operation}
}
)
};
return (
<View style={styles.container}>
<Text style={styles.title}>Choose a Operation:</Text>
<Button title="Addition" onPress={() => handleStartQuiz('addition')} />
<Button title="Subtraction" onPress={() => handleStartQuiz('subtraction')} />
<Button title="Multiplication" onPress={() => handleStartQuiz('multiplication')} />
<Button title="Division" onPress={() => handleStartQuiz('division')} />
</View>
);
};
const styles = StyleSheet.create({
container: {
display:'flex',
marginTop:60,
justifyContent: 'center',
// alignItems: 'center',
rowGap:10,
margin:20
},
title: {
fontSize: 20,
marginTop: 20,
},
});
export default HomeScreen;
Now create a new folder app/quiz
and inside of it create a dynamic route [id].tsx
[id].tsx :-
// QuizScreen.js
import { useLocalSearchParams } from 'expo-router';
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, StyleSheet, SafeAreaView } from 'react-native';
const QuizScreen = () => {
const { id } = useLocalSearchParams<{id:string}>();
const operation = typeof id === 'string' ? id : 'addition'; // Provide a default operation if id is undefined or not a string
const [num1, setNum1] = useState(0);
const [num2, setNum2] = useState(0);
const [userAnswer, setUserAnswer] = useState('');
const [score, setScore] = useState(0);
const [time, setTime] = useState(10);
useEffect(() => {
console.log('Operation from params:', operation);
generateQuestion();
}, [operation]);
const generateQuestion = () => {
switch (operation) {
case 'addition':
setNum1(Math.floor(Math.random() * 100) + 1);
setNum2(Math.floor(Math.random() * 100) + 1);
break;
case 'subtraction':
setNum2(Math.floor(Math.random() * 100) + 1);
setNum1(Math.floor(Math.random() * 100) + 1); // Adjusted to ensure num2 is generated properly
break;
case 'multiplication':
setNum1(Math.floor(Math.random() * 100) + 1);
setNum2(Math.floor(Math.random() * 10) + 1);
break;
case 'division':
const divisor = Math.floor(Math.random() * 9) + 1;
const quotient = Math.floor(Math.random() * 100) + 1;
setNum2(divisor);
setNum1(divisor * quotient);
break;
default:
setNum1(0);
setNum2(0);
}
};
const handleAnswerChange = (text:string) => {
setUserAnswer(text);
const answer = calculateAnswer();
const tolerance = 0.0001; // Adjust tolerance level as needed
if (Math.abs(parseFloat(text) - answer) <= tolerance) {
setScore(score + 1);
handleNextQuestion();
}
};
const calculateAnswer = () => {
switch (operation) {
case 'addition':
return num1 + num2;
case 'subtraction':
return num1 - num2;
case 'multiplication':
return num1 * num2;
case 'division':
return num1 / num2; // Ensure it's a precise division
default:
return num1 + num2; // Default to addition
}
};
const handleNextQuestion = () => {
generateQuestion();
setUserAnswer('');
setTime(10);
};
useEffect(() => {
const timer = setInterval(() => {
setTime((prevTime) => {
if (prevTime > 0) {
return prevTime - 1;
} else {
handleNextQuestion();
return 10; // Reset timer to 10 seconds for the next question
}
});
}, 1000);
return () => clearInterval(timer);
}, []);
return (
<SafeAreaView style={styles.container}>
<Text style={{ fontWeight: 'bold', fontSize: 38 }}>Speed Math</Text>
<View style={styles.topBar}>
<View>
<Text style={styles.timer}><Text>⌛</Text> {time} sec</Text>
</View>
<Text style={styles.score}>Score: {score}</Text>
</View>
<Text style={styles.question}>
{num1} {getOperationSymbol(operation)} {num2} =
</Text>
<TextInput
style={styles.input}
keyboardType="numeric"
value={userAnswer}
onChangeText={handleAnswerChange}
autoFocus={true}
/>
</SafeAreaView>
);
};
const getOperationSymbol = (operation:string) => {
switch (operation) {
case 'addition':
return '+';
case 'subtraction':
return '-';
case 'multiplication':
return '×';
case 'division':
return '÷';
default:
return '+';
}
};
const styles = StyleSheet.create({
container: {
marginTop:50,
flex: 1,
alignItems: 'center',
},
question: {
fontSize: 20,
marginTop: 200,
marginBottom: 10,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 20,
textAlign: 'center',
width: 100,
},
timer: {
marginTop: 10,
fontSize: 16,
fontWeight: 'bold',
},
score: {
marginTop: 10,
fontSize: 16,
fontWeight: 'bold',
},
topBar: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 30,
alignItems: 'center',
width: 360,
},
});
export default QuizScreen;
*Important points *
- Here,
useLocalSearchParams
fromexpo-router
is used to extract query parameters from the URL. -
useLocalSearchParams
extracts theid
parameter from the URL, which determines the type of arithmetic operation. - operation sets the operation type based on
id
or defaults to "addition". - We initialize state variables:
num1
,num2
,userAnswer
,score
, andtime
.
Function to Generate Questions
const generateQuestion = () => {
switch (operation) {
case 'addition':
setNum1(Math.floor(Math.random() * 100) + 1);
setNum2(Math.floor(Math.random() * 100) + 1);
break;
case 'subtraction':
setNum2(Math.floor(Math.random() * 100) + 1);
setNum1(Math.floor(Math.random() * 100) + 1);
break;
case 'multiplication':
setNum1(Math.floor(Math.random() * 100) + 1);
setNum2(Math.floor(Math.random() * 10) + 1);
break;
case 'division':
const divisor = Math.floor(Math.random() * 9) + 1;
const quotient = Math.floor(Math.random() * 100) + 1;
setNum2(divisor);
setNum1(divisor * quotient);
break;
default:
setNum1(0);
setNum2(0);
}
};
I'm new to react native and maybe not that good at explaining the code. Still, you can reach out to me
LinkedIn
Live Apk
Code
Top comments (0)