Когда ре-рендер нужен, но никак не случается
Я уже сталкивалась с этим и уже удивлялась этому поведению, но позабыла — и поэтому сегодня потратила на отладку пару часов: подозревала в коварстве библиотеку компонентов, которую использую, искала проблемы в композиции и отлаживала изменения контекста.
Итак, что было? В нашем 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 следят за изменениями локации, поэтому как только мы их используем — наш компонент начинает пере-рендериваться при изменениях
Самое противное, что у меня уже была подобная проблема, но вспомнила о ней и об этом моменте я только тогда, когда “заново” нашла решение. Записываю сюда, чтобы больше не забывать 🤪