'Refactoring a memory numbers game in React

I've been learning React by doing simple games.

I'm doing a memory game which is, for the most part, finished and I'm not sure how I should refactor the code to be better.

The user task is basically to remember the numbers flashed, and then click them, until the user can't hold them in memory anymore.

How should I divide/refactor the code?

Also, how would you go forward with games like this? Would you perhaps create a gameEngine or such? Although this would be the part of refactoring right. I'm using React for games because I am learning React and improving it.

Code:

import React, { useState, useEffect } from 'react';

import Cell from './sub-components/Cell/Cell';

import './styles.scss';


function BoardScene() {

    const [gameNumbers, setGameNumbers] = useState([]);
    const [isPlayerTurn, setIsPlayerTurn] = useState(false);

    const [currentScore, setCurrentScore] = useState(0);
    const [bestScore, setBestScore] = useState(0);

    const [flashCard, setFlashCard] = useState(null);
    const [clickedNumber, setClickedNumber] = useState(null);
    const [currentUserIndex, setCurrentUserIndex] = useState(0)


    function startGame() {
        addNewNumber()
    }

    function resetGame() {
        setGameNumbers([])
        setCurrentUserIndex(0)
        if (bestScore < gameNumbers.length) return setBestScore(gameNumbers.length)
    }


    const blinkCell = () => {
        let count = 0;

        setIsPlayerTurn(false);
        const timerID = setInterval(() => {
            console.log("Inside interval before if")
            console.log("currentUserIndex", count, gameNumbers.length)

            if (count === gameNumbers.length) {
                setIsPlayerTurn(true);
                console.log("Inside time out if statement")
                clearInterval(timerID);
                count = 0;
                setFlashCard(null);
            } else {
                setFlashCard(gameNumbers[count]);
                count++;
            }

        }, 500);
    };

    function generateRandomNumber(min, max) {
        return Math.floor(Math.random() * (max - min) + min);
    }

    function addNewNumber() {
        console.log("Add new number")
        let memoryNumber = generateRandomNumber(1, 9)
        setGameNumbers(gameNumbers => [...gameNumbers, memoryNumber])
    }

    function clickedNumberHandle(number) {
        console.log("Clicked number", number)
        setClickedNumber(number)
        isMatch(number)
    }

    function isMatch(number) {
        if (number === gameNumbers[currentUserIndex]) {
            console.log("Correct")

            if (currentUserIndex + 1 === gameNumbers.length) {
                setCurrentUserIndex(0)
                console.log("set current index 0")
                addNewNumber()
                blinkCell();
            } else {
                console.log("set current index + 1")
                setCurrentUserIndex(currentUserIndex + 1)
            }

        } else {
            resetGame()
            console.log("game over")
        }
    }

    useEffect(() => {
        blinkCell()
        console.log("Use effect start blinkCell")
    }, [gameNumbers])

    return (
        <>
            <div className="game-container">

                <div className="testing-stuff">
                    <div>
                        <button onClick={startGame}>Start Game</button>
                    </div>
                    <div onClick={addNewNumber}>
                        <button>Add new number</button>
                    </div>
                    <div>
                        <span>Game numbers: </span>
                        {gameNumbers.map((item, i) => {
                            return <span key={i}>{item}</span>
                        })}
                    </div>
                    <div>
                        <span>User Turn: {isPlayerTurn ? "True" : "False"}</span>
                    </div>
                    <div>
                        <span>Score: {gameNumbers.length}</span>
                    </div>

                </div>



                <div className="board">
                    {Array(9).fill().map((x, i) => {
                        return (
                            <Cell key={i} onClick={() => clickedNumberHandle(i + 1)} number={i + 1} active={i + 1 === flashCard ? true : false} />
                        )
                    })}
                </div>

                <div className="stats">
                    <div className="stats__score-wrap">
                        <span>Score: </span><span>{gameNumbers.length}</span>
                    </div>
                    <div className="stats__bestScore-wrap">
                        <span>Best Score: </span><span>{bestScore}</span>
                    </div>
                </div>

            </div>
        </>
    );
}

export default BoardScene;

GitHub Code: https://github.com/AurelianSpodarec/memory-numbers-game/blob/e177dfdaafe5daf393f1ae8fcee0827a16474e8f/src/scenes/BoardScene/BoardScene.js

Live Game: https://memory-numbers-game.netlify.app/



Solution 1:[1]

I'm no expert but I would personally re-factor to use ES6 syntax for all your functions. At the moment some of them are using arrow functions and some aren't.

I'd also personally have functions that are performing generic actions you might want to use across your codebase (such as generateRandomNumber) in their own modules.

On a specific point that isn't an opinion. I would not use the index (i) of your array as a key. This can lead to many issues. I would look into using a library like UUID to generate these keys.

Other than that looks again, and again I'm no expert. If you want some more info from someone who is I'd recommend Kent C Dodd blog and Ben Awads Youtube channel.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 msmoore