Search code examples
reactjsredux

Redux store data update implies a full refresh of React components?


Giving the following code:

export const globalsSlice = createSlice({
    name: 'globals',
    initialState: {
        testDirectories: {
            dir_a: {
                dir_a1: {},
                dir_a2: {
                    dir_a2a: {}
                }
            }
        }
    },
    reducers: {
        updateDirectory: (state, action) => {
            AddDirectorySubdir(state.testDirectories, action.payload.directoryName, action.payload.newSubDirectory);
            return state;
        }
    }
});

function AddDirectorySubdir(tree, directoryName, newSubDirectory) {
    for (var subDirectory in tree) {
        if (subDirectory == directoryName) {
            tree[subDirectory].newSubDirectory = {};
            return true;
        }
        else if (AddDirectorySubdir(tree[subDirectory], directoryName, newSubDirectory))
            break;
    }

    return false;
}

and:

function DirectoryDisplay(props) {
    console.log("Refreshing " + props.directory);

    function handleUpdateDirectory() {
        let paylod = { directoryName: props.directory, newSubDirectory: "new_subDir" }
        store.dispatch(updateDirectory(paylod));
    }

    let directoryStructure = [], subDirectoriesDepth = props.depth + 1;
    directoryStructure.push(
        <div id={props.directory} style={{ marginLeft: props.depth * 10, border: "1px solid black", width: "130px" }}>      
            {props.directory}           
            <button style={{ marginLeft: 10 }} onClick={() => handleUpdateDirectory()}>Update</button>
        </div>
    );
    for (let subdirectory in props.subDirectories) {
        directoryStructure.push(<DirectoryDisplay directory={subdirectory} subDirectories={props.subDirectories[subdirectory]} depth={subDirectoriesDepth}/>)
    }

    return directoryStructure;
}

export default function App() {
    const testDirectories = useSelector(state => state.globals.testDirectories);

    let rootDirectoryName = Object.keys(testDirectories)[0];
    return (
        <Fragment>
            <DirectoryDisplay directory={rootDirectoryName} subDirectories={testDirectories[rootDirectoryName]} depth={0} />
        </Fragment>
  );
}

The output is as follows:

First rendering

with the following console logs:

Refreshing dir_a
Refreshing dir_a1
Refreshing dir_a2
Refreshing dir_a2a

Then, when I click on the blued button, I got the desired following result:

Second rendering

with the following console logs:

Refreshing dir_a
Refreshing dir_a1
Refreshing dir_a2
Refreshing dir_a2a
Refreshing newSubDirectory

And there is what I was not expecting. the store state has changed, indeed, and the updating directory tree descends all the <DirectoryDisplay /> components as its hook is held by the root component. What I don't understand is that only the very last one "dir_a2a" has his content physically changed, so why are they all rerendered? Is there a way to prevent such a full rerendering? Should I manually consider a ComponentShouldUpdate() (or whatever it is for functional components) for each components?


Solution

  • If you have a tree and add a branch to a higher branch then the parent will re render but won't re render all children (only the one that changed).

    You can use React.memo to make a functional component pure here is an example of a tree that only re renders things that change.