Search code examples
reactjstypescriptreact-hooksreact-context

How can I prevent unnecessary re-renders on the child components in React with Hooks & Context?


I'm working on some code, and this code is huge. We have so many child components(nearly 300) and each one of them are using & manipulating values from the parent component's state via React Context.

Note: I didn't wrote this code from scratch. Which also means the design I'm about to show is not what I would come up with.

The problem: Since every component is using the same state, there are so many unnecessary re-renders happening. Every small state change is causing every component to re-render. And it makes the web app laggy. Literally, there is lag when you enter some input in a field.

I think, when state change happens the functions get rebuilt and that's why every child gets updated, because the value provided by context is changed after state change happened.

Additionally, I tried to use useReducer instead of useState that didn't went well. Besides that, I tried to use React.memo on every child component but the compare function didn't get triggered no matter what I tried. compare function only got triggered on the parent component which has the state as props. At this point, I'm not even really sure what is the problem :D

To give more specific details on the design, here is how we define and pass the callbacks to child components.

Definitions:

const [formItemState, setFormState] = React.useState<FormItemState>({} as FormItemState);

const getAppState = useCallback(() => ({ state: props.state as AppState }), [props.state]);

const getAppAction = useCallback(() => ({ action: props.action as AppAction }), [props.action]);

const getFormItemError = useCallback((key: string) => formItemErrorState[key], [
  formItemErrorState,
]);

const getFormItem = useCallback((key: string) => formItemState[key], [formItemState]);

const updateFormItem = useCallback(
    (name: string, formItemData: FormItemData): void => {
        const previousState = getFormItem(name);
        if (!isEqual(previousState, formItemData)) {
            formItemState[name] = formItemData;
            setFormState((state) => ({
                ...state,
                ...formItemState,
            }));
        }
    },
    [formItemState, setFormState]
);

Passing them to Context.Provider:

return (
    <FormContext.Provider
        value={{
            getAppAction,
            getAppState,
            getFormItem,
            updateFormItem
        }}
    >
        <SomeComponent>
            {props.children} // This children contains more than 250 nested components, and each one of them are using these provided functions to interact with the state.
        </SomeComponent>
    </FormContext.Provider>
);

Last note: Please ask me if more info is needed. Thanks!


Solution

  • Why dont you just rewrite your state management to redux and pull only the necessary state to be used on each component. React.memo only picks up changes from props