Перейти к содержимому

Когда ре-рендер нужен, но никак не случается

Я уже сталкивалась с этим и уже удивлялась этому поведению, но позабыла — и поэтому сегодня потратила на отладку пару часов: подозревала в коварстве библиотеку компонентов, которую использую, искала проблемы в композиции и отлаживала изменения контекста.

Итак, что было? В нашем App.tsx:

<Router history={history}>
<UuiContext.Provider value={services}>
<Header />
<Routes />
<Footer />
</UuiContext.Provider>
</Router>

В Header.tsx — навигация:

// компонент из библиотеки, у него есть подсветка активного пункта меню.
// заглядывает в uuiContext от UuiContext.Provider и проверят, активная ли линка через history
<MainMenu>
<MainMenuButton caption='Home' link={{pathname: '/' } />
<MainMenuButton caption='Dashboard' link={{pathname: '/dashboard' } />
</MainMenu>

И вроде всё (ну, почти всё) работает: по кликам в MainMenu урл обновляется, контент перерисовывается. Вот только нет выделения активного элемента в MainMenu.

Начинаю разбираться, почему — ставлю точку остановы в фукнцию в MainMenu.tsx, где определяется, активная ли линка. И тут начинается интересное: при переходе по кнопкам и обновлении урла функция не вызывается.

Добавляю console.log(‘render’) в Header.tsx. Что ж, он не пере-рендеривается, так что логично, что ничего не происходит. Но ведь мы же вроде обновляем историю, а в uuiContext.uuiRouter.history — как раз всё обновляется… Почему же дочерние элементы UuiContext.Provider не обновляются?

Тут я пошла неверным путём: попыталась отладиться в контекстах, потому что заподозрила, что у меня где-то создается несколько инстансов и происходит путаница. Ничего не нашла, естественно.

После блуждания по контекстам, в мою голову пришла мысль: содержимое страницы обновляется за счет изменения актуального Route — поэтому часть в Routes перерендеривается нормально. Что, если Header положить внутрь каждой страницы, соответствующей роуту? Это помогло, однако в таком случае Header каждый раз при изменении роута unmount и mount. Выглядит некрасиво и излишне.

И вот тут наконец хороший вопрос пришел в голову: как React Router определяет, что надо что-то пере-рендерить? И вот что я выяснила:

  • мы обновляем history
  • history object имеет постоянную ссылку
  • useHistory поэтому все время будет возвращать один и тот же объект, поэтому useEffect c history.location не отработает при изменении location
  • зато вот useLocation и useParams следят за изменениями локации, поэтому как только мы их используем — наш компонент начинает пере-рендериваться при изменениях

Самое противное, что у меня уже была подобная проблема, но вспомнила о ней и об этом моменте я только тогда, когда “заново” нашла решение. Записываю сюда, чтобы больше не забывать 🤪