Search code examples
javascriptreactjsreact-hooksreact-contextuse-reducer

Prevent Modal Rerendering when only Modal Content Change: React Hooks useReducer


I have a parent-child component Modal and ModalContent in my React App both functional.

1) I have created an AppContext in App.js for accessing it globally in all components.

const [state, dispatch] = useReducer(reducer, {modalOpen: false, content: []});

    <AppContext.Provider value={{ state, dispatch }} >
      //BrowserRouter and Routes Definition
    </App>

Here reducer is a fairly plain function with toggle for modalOpen and push/pop functionality in content(array).

2) My Modal Component makes use of

const { state, dispatch } = useContext(AppContext); 
<Modal open={state.modalOpen} />

to get the state of modal visibility to set it open/closed.

3) My ModalContent Component makes use of

const { state, dispatch } = useContext(AppContext); 
<ModalContent data={state.content} />
  //Dispatch from the `ModalContent`
  dispatch({ type: 'UPDATE_CONTENT', data: 'newata' });

4) Here is my reducer.

export const reducer = (state, action) => {
switch (action.type) {
    case 'TOGGLE_MODAL':
            return {...state, modalOpen: !state.modalOpen};
    case 'UPDATE_CONTENT':
        return { ...state, content: [...state.content, action.data]};
    default:
        return {modalOpen: false,content: []};
 }
} 

I have set up some code in ModalContent to update the data content property using dispatch and the reducer store is updated perfectly and returns fresh/updated:

 {modalOpen: true/false, content: updatedContentArray}

The issue is: Whenever I dispatch an action via ModalContent complete state is returned(expected), by the reducer and Modal reopens itself as it listens to state.modalOpen.

Unsuccessful Attempt: I attempted to specifically pull required properties in respective components. But the Modal component still rerenders even when just content is changed. Is there any way to watch for specific state only

How can make this work by rerendering only ModalContent not Modal.

Edit: Updated question with my mock(working) reducer code and dispatch statement from the ModalContent itself.


Solution

  • The reason both Modal and ModalContent get re-rendered when Content changes is because both the components make use of the same context and when a context value changes all components listening to the context are re-rendered

    A way to fix this re-rendering thing is to make use of multiple contexts like

     const modalContextVal = useMemo(() => ({ modalOpen: state.modalOpen, dispatch}), [state.modalOpen]);
       const contentContextVal = useMemo(() => ({ content: state.content, dispatch}), [state.content]);
       ....
       <ModalContext.Provider value={modalContextVal}>
          <ContentContext.Provider value={contentContextVal}>
          //BrowserRouter and Routes Definition
          </ContentContext.Provider>
        </ModalContext.Provider>
    

    And use it like

    In Modal.js

    const {modalOpen, dispatch} = useContext(ModalContext);
    

    In ModalContent.js

    const {content, dispatch} = useContext(ContentContext);