pyan
pyan

Reputation: 885

Async run function after useEffect

I was wondering how it is possible to run a function after you use the useEffect to fetch data, where the function is manipulating the data after its been pulled?

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

const Result = (props) => {
    const [ playerName, setPlayerName ] = useState('');
    const [ playerChoice, setPlayerChoice ] = useState(null);
    const [ computerChoice, setComputerChoice ] = useState(null);
    const [ result, setResult ] = useState(null);

    useEffect(() => {
        setPlayerName(props.location.state.playerName);
        setPlayerChoice(props.location.state.playerChoice);
        setComputerChoice(generateComputerChoice);
        setResult(getResult())
    }, []);

    const getResult = () => {
        // code that runs after the setting of the playerName and playerChoice. Will return "Win", "Lose", or "Draw"

    };

    const generateComputerChoice = () => {
        const outcomes = [ 'Rock', 'Paper', 'Scissors' ];
        return outcomes[Math.floor(Math.random() * outcomes.length)];
    };

    return (
        <div className="app-container">
            <strong>YOU {result}</strong>
            <br />
            <strong>{playerName}</strong> chose <strong>{playerChoice}</strong>
            <br />
            <strong>Computer</strong> chose <strong>{computerChoice}</strong>
        </div>
    );
};

export default Result;

So here in this example I grab the playerName and playerChoice from a previous page, and then add that to my useState on page load.

After that I randomly generate the computerChoice.

However, after that I want to use the playerChoice amd computerChoice that has been added to the state and use that to see if the game is a win, lose or draw.

result ends up being null because I assume that by the time the getResult function is called, the states haven't been set yet.

Do you guys know what should be done in this situation? Seems like this could be a common thing considering you might wanna grab data from an API and then do something with that data before wanting to render it.

Upvotes: 2

Views: 15143

Answers (3)

Drew Reese
Drew Reese

Reputation: 203418

Use the useMemo hook and add the state variables to its dependency array. It will memoize the result for each render cycle so it is only ever computed when playerName or playerChoice update.

const getResult = useMemo(() => {
    // code that runs after the setting of the playerName and playerChoice. Will return "Win", "Lose", or "Draw"

}, [playerName, playerChoice]);

Oops, I see now you're trying to save this to the result state variable, so you can either use a second useEffect with the same dependencies instead of the useMemo I suggested, or in your original snippet instead of calling a getResult() function you change the signature to getResult(name, choice) and call setResult with the current render cycle values (right from the props).

useEffect(() => {
    const { playerName, playerChoice } = props.location.state;
    setPlayerName(playerName);
    setPlayerChoice(playerChoice);
    setComputerChoice(generateComputerChoice);
    setResult(getResult(playerName, playerChoice));
}, []);

const getResult = (name, choice) => {
    // Will return "Win", "Lose", or "Draw"
};

Upvotes: 1

Praneeth Paruchuri
Praneeth Paruchuri

Reputation: 1032

setState is asynchronous and you'll be able to use the state only in the next render. Unlike in class components, hooks doesn't allow a callback after setting the state.

But looking at your component and assuming that is what the functionality of that would be, there's no reason for you to add playerName and playerChoice to the state of the component. You can use the data from the props itself.

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

const Result = (props) => {
    const {playerName, playerChoice} = props.location.state;
    const [ result, setResult ] = useState(null);
    const [computerChoice, setComputerChoice] = useState(null);

    useEffect(() => {
        setComputerChoice(generateComputerChoice());
        getResult();
    }, []);

    const getResult = () => {

     // You can get the playerName, playerChoice from the props.

     // You can also setResult here.

    };

    const generateComputerChoice = () => {
        const outcomes = [ 'Rock', 'Paper', 'Scissors' ];
        return outcomes[Math.floor(Math.random() * outcomes.length)];
    };

    return (
        <div className="app-container">
            <strong>YOU {result}</strong>
            <br />
            <strong>{playerName}</strong> chose <strong>{playerChoice}</strong>
            <br />
            <strong>Computer</strong> chose <strong>{computerChoice}</strong>
        </div>
    );
};

export default Result;

Hope this helps.

Upvotes: 0

Jonas Wilms
Jonas Wilms

Reputation: 138527

That first effect is unneccessary. Just do

  const [playerName, setPlayerName] = useState(props.location.state.playerName);

Upvotes: 1

Related Questions