skip to Main Content

I have a list component that rendered by the Map, this is how I define the data in react function component:

const [projMap, setProjMap] = useState<Map<number, FolderModel>>(new Map<number, FolderModel>());

When I set the map in react function component like this:

React.useEffect(() => {
    if (folderProjList && folderProjList.length > 0) {
        if (currFolder) {
            let cachedExpand = projMap.get(currFolder.id)?.expand;
            let folderModel: FolderModel = {
                expand: cachedExpand ? cachedExpand : true,
                projects: folderProjList
            };
            projMap.set(currFolder.id, folderModel);
            setProjMap(projMap);
        }
    }
}, [folderProjList]);

did not trigger the rerender where using the projMap to render UI. Then I change the style like this:

React.useEffect(() => {
        if (folderProjList && folderProjList.length > 0) {
            if (currFolder) {
                setProjMap((prevMapState) => {
                    const newMapState = new Map<number, FolderModel>(prevMapState);
                    let cachedExpand = newMapState.get(currFolder.id)?.expand;
                    let folderModel: FolderModel = {
                        expand: cachedExpand ? cachedExpand : true,
                        projects: folderProjList
                    };
                    newMapState.set(currFolder.id, folderModel);
                    return newMapState;
                });
            }
        }
    }, [folderProjList]);

the rerender works fine. the UI was always keep to newest. what is the different with the two type of setState? why the second style works with immediate rerender but the fisrt style did not?

2

Answers


  1. In the first one you’re mutating the map (by adding an entry) and then setting the mutated map as state, so the state didn’t actually change (referentially), it’s the same map, it just got mutated. When the state doesn’t referentially change, react doesn’t re-render.

    In the second example, you’re creating a new map and setting the new map as state, no mutation, referentially different.

    Don’t mutate state.

    Login or Signup to reply.
  2. In the first example, you are only mutating the Map. Map is a reference type of data meaning adding or removing something from the it will not trigger React update.
    Consider this small example

    let x = new Map();
    let y = x; // `y` still holds the same reference that `x` does
    y.b = 20; // Adding some random values to `y`
    console.log("Result of x == y is:", x == y); // Logs: `Result of x == y is: true`
    

    At the same time if you do something like this

    let x = new Map();
    x.a = 10
    let y = new Map(x); // Creating a new instance of a new map using the values of `x`
    y.b = 20;
    console.log("Result of x == y is:", x == y); // Logs: `Result of x == y is: false`
    

    React only does a shallow comparison like above to understand whether it requires a rerender or not.

    So, if you want to update the state so that React does a rerender, you have to create a new instance. Your 1st example can also be reused like this

    React.useEffect(() => {
        if (folderProjList && folderProjList.length > 0) {
            if (currFolder) {
                let cachedExpand = projMap.get(currFolder.id)?.expand;
                let folderModel: FolderModel = {
                    expand: cachedExpand ? cachedExpand : true,
                    projects: folderProjList
                };
                projMap.set(currFolder.id, folderModel);
                setProjMap(new Map<number, FolderModel>(projMap)); // Changed here
            }
        }
    }, [folderProjList]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search