Search code examples
reactjssynchronoususe-effect

React js: run effects (useEffect) with common concerns synchronously


I have two effects set up to instantiate some values in my stateless react component. However, I can't seem to access the data set by my first function in my second function.

From my reading it seems that these may be running asynchronously, so it isn't guaranteed when each of the functions will run. How do I get effects with common concerns to run synchronously when they depend on each others effects? Alternatively, is there actually a better practice for replacing a constructor in a stateless react component?

Relevant code:

const [roadTiles, setRoadTiles] = useState([]);

...

useEffect(() => {
    populateRoadTiles(); // calls setRoadTiles() -- console.log shows roadTiles has been set
    populateRoadTileDivs(); // uses roadTiles -- roadTiles is still [] and causes index error
}, [])

I searched around for similar questions, but most of the solutions came down to individual bugs in the user's code. Hopefully this better distills the question.

EDIT:

Chaining dependencies seems to be the way to go based on Daniil's comment and the documentation.

However, I'm still running into the same problem. Maybe it's an issue with my logic in populateRoadTiles instead (below). The documentation seems to indicate that set*() functions are also async, so it could make sense that roadTiles is still unset after populateRoadTiles.

BUT, with the new dependency structure, populateRoadTileDivs is eventually called. This seems to indicate that roadTiles should have been set and triggered the second effect.

    const populateRoadTiles = () => {
        const newRoadTiles = new Array(height);
        for (let r = 0; r < height; r++) {
            newRoadTiles[r] = new Array(width);
        }
        for (let r = 0; r < height; r++) {
            for (let c = 0; c < width; c++) {
                newRoadTiles[r][c] = (
                    <RoadTile
                        onClick={() => getNeighbors(r, c)}
                        type={RoadTileType.EMPTY}
                    />
                );
            }
        }
        setRoadTiles(newRoadTiles);
        console.log(newRoadTiles); // populated array
        console.log(roadTiles); // []
    };

Solution

  • Each useEffect has [dependency] or has not [] === componentDidMount.

    React run the effect after its the dependency changed.

    const [roadTiles, setRoadTiles] = useState([]);
    const [roadTilesDivs, setRoadTilesDivs] = useState([]);
    
    ...
    
    useEffect(() => {
        populateRoadTiles(); // calls setRoadTiles() -- console.log shows roadTiles has been set
    }, [])
    
    useEffect(() => {
        populateRoadTileDivs(); // uses roadTiles -- roadTiles is still [] and causes index error
    }, [roadTiles]) // <- add dependency here
    

    if you want to log state keep it variable

        setRoadTiles(newRoadTiles); 
        console.log(newRoadTiles); // not actual, but it will be correct 
        console.log(roadTiles); // [] actual but it's prematurely.