Предотвращение модального повторного рендеринга, когда изменяется только модальное содержимое: React Hooks useReducer

У меня есть родительско-дочерний компонент Modal и ModalContent в моем приложении React. Оба они работают.

1) Я создал AppContext в App.js для доступа к нему глобально во всех компонентах.

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

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

Здесь reducer - это довольно простая функция с переключателем для modalOpen и функциональными возможностями push / pop в содержимом (массив).

2) Мой Modal Компонент использует

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

чтобы получить состояние модальной видимости, чтобы установить его открыто / закрыто.

3) Мой ModalContent Компонент использует

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

4) Вот мой редуктор.

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: []};
 }
} 

Я установил некоторый код в ModalContent для обновления свойства data content с помощью диспетчеризации, и хранилище редуктора обновляется идеально и возвращает свежий / обновленный:

 {modalOpen: true/false, content: updatedContentArray}

Проблема: всякий раз, когда я отправляю действие через ModalContent состояние завершения, возвращается (ожидается) редуктором, и Modal повторно открывается, когда он прослушивает state.modalOpen.

Неудачная попытка: я попытался получить необходимые свойства в соответствующих компонентах. Но компонент Modal по-прежнему выполняет повторную визуализацию, даже если изменяется только содержимое. Есть ли способ следить только за определенным состоянием

Как можно заставить эту работу работать, выполняя повторную визуализацию только ModalContent, а не Modal.

Изменить: обновлен вопрос с моим макетом (работающим) кодом редуктора и оператором отправки из самого ModalContent.


person nikhil024    schedule 30.05.2020    source источник
comment
Можете ли вы показать свой редуктор и диспетчерский вызов. Также проблема заключается в том, что Modal выполняет повторный рендеринг или что openModal неправильно получает значение modelOpen как true   -  person Shubham Khatri    schedule 30.05.2020
comment
Вы можете улучшить Modal с помощью React.memo, чтобы он был мемоизированным компонентом, который не будет повторно отображаться, если state.modalOpen имеет то же значение.   -  person ford04    schedule 30.05.2020
comment
@ShubhamKhatri Я добавил свою более простую версию своего редуктора (работающего) и добавил вызов диспетчеризации в приведенный выше фрагмент кода. OpenModal работает нормально, как я вижу, когда я закрываю модальное окно, оно закрывается и для этого обновляется состояние false.   -  person nikhil024    schedule 30.05.2020
comment
хорошо, значит, проблема заключается только в повторном рендеринге модального компонента, когда изменяется только контент?   -  person Shubham Khatri    schedule 30.05.2020
comment
Да @ShubhamKhatri   -  person nikhil024    schedule 30.05.2020


Ответы (2)


Причина, по которой и Modal, и ModalContent повторно визуализируются при изменении содержимого, заключается в том, что оба компонента используют один и тот же контекст, а при изменении значения контекста все компоненты, слушающие контекст, повторно визуализируются.

Способ исправить эту проблему повторного рендеринга - использовать multiple contexts как

 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>

И используйте это как

В Modal.js

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

В ModalContent.js

const {content, dispatch} = useContext(ContentContext);
person Shubham Khatri    schedule 30.05.2020
comment
Идеальный @shubham, сработал как шарм, на самом деле с этим подходом я даже могу получить доступ к нескольким контекстам в одном компоненте (при необходимости), не беспокоясь о повторном рендеринге, пока я специально не обновлю этот кусок состояния! - person nikhil024; 30.05.2020

Как сказал @Shubham, вы должны разделять модальное состояние и модальное содержимое.

Это можно сделать с отдельным контекстом или даже с помощью простого useState

Пример фрагмента

const { useReducer, createContext, useContext, useState, useEffect, memo, useMemo } = React;

const AppContext = createContext();

const reducer = (state, action) => {
  if(action.type == 'toggleModal') {
    return {
      ...state,
      modalOpen: !state.modalOpen
    }
  }
  
  return state;
}

const AppContextProvider = ({children}) => {
  const [state, dispatch] = useReducer(reducer, {modalOpen: false, content: [{id: 1, value: 'test'}]});

  return <AppContext.Provider value={{state, dispatch}}>
    {children}
  </AppContext.Provider>
}

const Modal = ({children, modalOpen}) => {
  const { state, dispatch } = useContext(AppContext); 

  console.log('Render Modal');

  return <div className={`modal ${modalOpen ? 'modal--open': null}`}>
    {children}
  </div>
}

const ModalContent = ({data, onClick}) => {

  console.log('Render Modal Content');
  
  return <div className="modal__content">
    {data.map(({id, value}) => <div className="item" key={id}>{value}</div>)}
    <button onClick={onClick} className="modal__close">Close</button>
  </div>
}

const App = () => {
  const { state, dispatch } = useContext(AppContext); 
  const { modalOpen, } = state;
  const [content, setContent] = useState([]);
  
  const onClick = () => {
    dispatch({ type: 'toggleModal' });
  }

  return <div>
    <Modal modalOpen={modalOpen}>
      {useMemo(() => {
  
  console.log('render useMemo');
  
  return <ModalContent onClick={onClick} data={content}></ModalContent>
  }, [content])}
    </Modal>
    <button onClick={onClick}>Open Modal</button>
  </div>
}

ReactDOM.render(
    <AppContextProvider>
      <App />
    </AppContextProvider>,
    document.getElementById('root')
  );
.modal {
  background: black;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  z-index: -1;
  transition: all .3s ease-in;
}

.modal__close {
  padding: .5rem 1rem;
  color: white;
  border: 1px solid white;
  background: transparent;
  cursor: pointer;
}

.modal--open {
  opacity: 1;
  z-index: 1;
}

.item {
  padding: 1rem;
  color: white;
  border: 1px solid white;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>

person Józef Podlecki    schedule 30.05.2020