DEV Community

Rohit Sharma
Rohit Sharma

Posted on

React Native Speed Math App

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

*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

    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);
        }
    };
Enter fullscreen mode Exit fullscreen mode

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)