Did you ever stumble upon a situation where it seems no matter what you do, your state does not get correctly updated?

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

const PokemonList = () => {
    const [pokemons, setPokemons] = useState([]);
    useEffect(async () => {
        const data = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=10`).then(response => response.json());
        setPokemons(data.results);
        console.log('New pokemons list ', pokemons);
    }, [])
    
    return <ul>{pokemons.map(pokemon => <li key={pokemon.name}>{pokemon.name}</li>)}</ul>
}

The API returns correctly, you update your state by calling setState, yet when you log the value, it always comes back empty?

Well, maybe everything works fine just that you're logging at the wrong time 🙈

Logging inside useEffect

When logging inside  useEffect, you're using the state value that was captured in the closure at the beginning of the render. So that's why you're always seeing the empty array.

Moving the console.log right before the return statement will instead look at the latest state value and log the updated list of pokemons 💥

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

const PokemonList = () => {
    console.log('======= RENDER START =======')
    const [pokemons, setPokemons] = useState([]);
    useEffect(async () => {
        const data = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=10`).then(response => response.json());
        console.log('API Returned:', data.results);

        setPokemons(data.results);
        console.log('State right after setPokemon: ', pokemons);
    }, [])
    
    console.log('State right before render: ', pokemons);
    return <ul>{pokemons.map(pokemon => <li key={pokemon.name}>{pokemon.name}</li>)}</ul>
}

Pause for a second - can you guess what the order of the console.logs is and what they will output?

...

...

======= RENDER START =======
State right before render:  []
API Returned: (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
======= RENDER START =======
State right before render:  (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
State right after setPokemon:  []

Let's do a step by step walkthrough of what happens in the code above

RENDER 1

  • state is []
  • a new function is created to be passed into useEffect
  • --> function creates a closer over the current value of state: []
  • effect is called
  • --> API is called to load the data
  • --> data is successfully retrieved
  • --> setState is called to update the pokemon list
  • --> --> RENDER 2 starts
  • --> console.log statement is called logging state value wrapped in closure - [] -> the old one!

RENDER 2

  • state is now the list of pokemons
  • a new function is created to be passed into useEffect
  • --> function creates a closure over the current value of state: the full list of pokemons
  • effect is on longer called, since it was defined with empty deps array`, which means it only gets run on the first render

So the console.log that outputs the empty array is actually a stale value, captured in the first render!

To make sure you really understand the flow, try drawing a diagram of the flow above with pen and paper!

I hope you found this helpful! Let me know in the comments below if you still have any questions 🙏