简体   繁体   中英

Updating state in async function

I'm very new to react so apologies if this question has already been answered or should be phrased differently. I have a functional component that fetches a.json file from my public folder in an async function (loadData()). Using the developer tools in my chrome window, I can see that the function gets me exactly what I want, yet the state doesn't seem to want to update when I use setData.

Edit: So I think I know what the problem is, which is that the first time the component renders, the variable source needs that JSON object which won't be there until the component re-renders. If that's the case, should all the code starting at let source = pickRandomVerb(data) go somewhere outside useEffect()?

function ComposedTextField() {

    const classes = useStyles();
    const [data, setData] = React.useState([]);
    const [displayVerb, setDisplayVerb] = React.useState('');

    const pickRandomVerb = (list) => {
        var obj_keys = Object.keys(list);
        var ran_key = obj_keys[Math.floor(Math.random() * obj_keys.length)];
        return list[ran_key];
    }

    const loadData = async() => {
        const response = await fetch('verbs.json');
        const json = await response.json();
        setData(json);
        console.log(json); //json is exactly what I want here
        console.log(data); //data is just '[]' here
    }

    useEffect(() => {

        loadData();
        console.log(data) //data is also '[]' here

        let source = pickRandomVerb(data)
        let verbSource = source.infinitive;
        let showVerb = verbSource.toString().replaceAll("\"", "");
        setDisplayVerb(showVerb)
    
    }, [])


    return(
        <div>
            <Typography className = {classes.form}>
                <p>{displayVerb}</p>
            </Typography>
        </div>
    )
}

Can anyone let me know what I'm missing? Thanks in advance.

setState is async also then you can't see the data changed in

    const loadData = async() => {
        const response = await fetch('verbs.json');
        const json = await response.json();
        setData(json);
        console.log(json); //json is exactly what I want here
        console.log(data); //data is just '[]' here
    }

Your useEffect doesn't define the dependency then it just run once. If you want to see your data changed just add dependency for data to track when data changed.

   /// Just load at the first time you render you component
   useEffect(()=>{
       loadData();
    },[])

    useEffect(() => {

        console.log(data)

        let source = pickRandomVerb(data)
        let verbSource = source.infinitive;
        let showVerb = verbSource.toString().replaceAll("\"", "");
        setDisplayVerb(showVerb)
    
    }, [data]) //Just add dependency here for useEffect

useState hook is also asynchronous, and will not be reflected immediately and this question already addressed link-1 , link-2

changes required (you have to listen the changes of the data in useEffect),

useEffect(() => {
   loadData();
    console.log(data) //data is also '[]' here
}, []);

useEffect(() => {
        let source = pickRandomVerb(data)
        let verbSource = source.infinitive;
        let showVerb = verbSource.toString().replaceAll("\"", "");
        setDisplayVerb(showVerb)
}, [data]);

Maybe you can try something like this

function ComposedTextField() {

    const classes = useStyles();
    const [data, setData] = React.useState([]);
    const [displayVerb, setDisplayVerb] = React.useState('');

    const pickRandomVerb = (list) => {
        var obj_keys = Object.keys(list);
        var ran_key = obj_keys[Math.floor(Math.random() * obj_keys.length)];
        return list[ran_key];
    }

    const loadData = async() => {
        const response = await fetch('verbs.json');
        const json = await response.json();
        setData(json);
        console.log(json); //json is exactly what I want here
        console.log(data); //data is just '[]' here
    }

    useEffect(() => {

        loadData();
        console.log(data) //data is also '[]' here
    
    }, [])

    // Move that code outside useEffect and inside condition
    if(data!==[]){
        let source = pickRandomVerb(data)
        let verbSource = source.infinitive;
        let showVerb = verbSource.toString().replaceAll("\"", "");
        setDisplayVerb(showVerb)
    }

    return(
        <div>
            <Typography className = {classes.form}>
                <p>{displayVerb}</p>
            </Typography>
        </div>
    )
}

In this code setDisplayVerb() will be called when the data has some value. The flow of the control would be something like this.

  1. Component is mounted with data=[]
  2. usEffect is called which in turns calls loadData()
  3. loadData will set the value of data using setData(json)
  4. When data is set it will cause re-render, since data has value this time, the if condition is satisfied and the statements inside it will be executed.
  5. setDisplayVerb() will cause re-render of the application.
  6. Since the dependency array of useEffect is empty it will not be called again.

This way you should be able to see the data on your screen.

so to expand on my comment, try this:

    const loadData = async() => {
        const response = await fetch('verbs.json');
        const json = await response.json();
        return json; /// <<< note: here you return data
    }

    useEffect(async () => {
        const data = await loadData(); // <<<< note: here you wait for data
        console.log(data);

        let source = pickRandomVerb(data)
        let verbSource = source.infinitive;
        let showVerb = verbSource.toString().replaceAll("\"", "");
        setDisplayVerb(showVerb)
    
    }, [])

this will only get you the data on initial load.

Just as a side note: to improve a bit on this component, you can move your loadData function outside of the component.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM