reactjsreact-hooksreact-stateside-effectspure-function

Is my usage of useEffect to generate array correct here?


I want to generate a 16-length array of random prizes using prizes array that is passed as a prop in Board component, and display them.

prizes array -

[
    {
        prizeId: 1,
        name: 'coupon',
        image: 'img/coupon.svg',
    },
    {
        prizeId: 2,
        name: 'gift card',
        image: 'img/gift-card.svg',
    },
    // more prizes
]

In Board.js -

const Board = ({ prizes }) => {
    const [shuffledPrizes, setShuffledPrizes] = useState(null)

    useEffect(() => {
        setShuffledPrizes(shuffleArray(populatePrize(16, prizes)))
    }, [prizes])

    return (
        <div>
            {
                shuffledPrizes && shuffledPrizes.map((prize) => (
                    <Prize
                        key={prize.id}
                        prize={prize}
                    />
                ))
            }
        </div>
    )
}

In populatePrize function, I have to add id to use as React key because already existed prizeId can't be used, as prizes will be duplicated -

import { nanoid } from 'nanoid'

const populatePrize = (noOfBlock, prizeArray) => {
    const arrayToPopulate = []

    let index = 0
    for (let i = 0; i < noOfBlock; i += 1, index += 1) {
        if (index === prizeArray.length) {
            index = 0
        }
        arrayToPopulate.push({
            id: nanoid(),
            prizeId: prizeArray[index].prizeId,
            name: prizeArray[index].name,
            image: prizeArray[index].image,
        })
    }

    return arrayToPopulate
}

Is using useState and useEffect necessary here? Because, I don't think generating an array and shuffling it is a side effect, and I can just use a variable outside of Board function like -

let shuffledPrizes = null

const Board = ({ prizes }) => {
    if (!shuffledPrizes)
        shuffledPrizes = shuffleArray(populatePrize(16, prizes))
    }

    return (
        <div>
            {
                shuffledPrizes.map((prize) => (
                    <Prize
                        key={prize.id}
                        prize={prize}
                    />
                ))
            }
        </div>
    )
}

But, with that way, every <Board /> component references and display the same shuffledPrizes array, not randomly for each Board component like I want.

Reusing Board is not a requirement, but I read in React docs about components being pure functions and I don't think mine is one. I am also confused in when to use a variable outside or inside of a component, and when to use state.

Although my question might be about using useEffect, I want to learn how to improve this code in proper React way.


Solution

  • This in indeed not a good use case of useEffect.

    Effects are an escape hatch from the React paradigm. They let you “step outside” of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM. If there is no external system involved (for example, if you want to update a component’s state when some props or state change), you shouldn’t need an Effect. Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone.

    You can shuffle the array when you pass it trough props.

    const BoardContainer = () => <div>
        <Board prizes={shuffleArray(populatePrize(16, prices))}/>
        <Board prizes={shuffleArray(populatePrize(16, prices))}/>
    </div>
    

    You can also use the lazy version of useState that is only evaluated during the first render

    const Board = ({prizes}) => {
        const [shuffledPrizes,] = useState(() => shuffleArray(populatePrize(16, prizes)))
    
        return (
            <div>
                <ul>
                    {
                        shuffledPrizes && shuffledPrizes.map((prize) => (
                            <Prize
                                key={prize.id}
                                prize={prize}
                            />
                        ))
                    }
                </ul>
            </div>
        )
    }