React Native Speed Math App
Tags: reactnative, javascript, beginners, mobile
Published: 2024-06-10 09:22:53

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
```js
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:

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

![Home Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zfcyvi9ohss8dptpcd36.jpg)

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.


![Quiz Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uqkhycv3793owaeof6qn.jpg)

`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 :-
```js
// 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 :-

```js
// 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` from `expo-router` is used to extract query parameters from the URL. 
- `useLocalSearchParams` extracts the `id` 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`, and `time`.

**Function to Generate Questions**

```js
    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](https://linkedin.com/in/rohit-sharma16)
[Live Apk](https://expo.dev/artifacts/eas/czLf4NMRMVWgvqkagNnxN9.apk)
[Code](https://github.com/dev-rohit0/speed-math)