I read that hakerrank has challenges organized by domain — for example, the React domain — and decided to solve them as part of my interview
preparation.
I completed all of them, but not satisfied. Here’s why:
The challenges don’t feel like real interviews. They give you a lot of
code, while in actual interviews you often write components almost from scratch.
The tests are not reliable. In one case, I had to remove the key from react element just to
make their test pass. Test kept a reference to the element at the start and didn’t update it
after re-render.
In another challenge, tests used hardcoded outdated dates, so I had to add a weird condition
just to pass.
The platform itself has issues. Sometimes my progress wasn’t saved. A couple of times I
submitted a solution, saw the “Congratulations” screen, went back to the challenge list — and it showed the challenge as unsolved. I had to redo the solution. Eventually, I started double-clicking the submit button just in case, because I didn’t want to retype everything again.
Overall, I regret the time spent a little, because mostly I fought with tests, not with
solving problem. Moreover, challenges turned out to be not so interesting. I liked leetcode
experience more.
Я уже сталкивалась с этим и уже удивлялась этому поведению, но позабыла — и поэтому сегодня потратила
на отладку пару часов: подозревала в коварстве библиотеку компонентов, которую использую,
искала проблемы в композиции и отлаживала изменения контекста.
Итак, что было? В нашем App.tsx:
<Routerhistory={history}>
<UuiContext.Providervalue={services}>
<Header />
<Routes />
<Footer />
</UuiContext.Provider>
</Router>
В Header.tsx — навигация:
// компонент из библиотеки, у него есть подсветка активного пункта меню.
// заглядывает в uuiContext от UuiContext.Provider и проверят, активная ли линка через history
И вроде всё (ну, почти всё) работает: по кликам в 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 следят за изменениями локации, поэтому как только мы их используем —
наш компонент начинает пере-рендериваться при изменениях
Самое противное, что у меня уже была подобная проблема, но вспомнила о ней и об этом моменте я только тогда,
когда “заново” нашла решение. Записываю сюда, чтобы больше не забывать 🤪
Вам когда-нибудь говорили, что вот так передавать массив — плохая практика?
<Componentlevels={[1, 2, 3]} />
Ведь массив будет пересоздаваться в JSX при каждом ре-рендере, то есть пропсы
будут меняться и запускать ненужные ре-рендеры Component?
И вы наверняка слышали, что нужно использовать useMemo (const levels = useMemo(() => [1, 2, 3], []))
или объявить массив вне компонента?
Я слышала это много раз.
И следовала этому принципу.
Даже когда я столкнулась с кейсами, что при передаче в пропсы ref объекта и обновлении этого
объекта потом — компонент не перерисовывается, я всё равно продолжала избегать передачи вот
таких массивов и объектов как пропсов. Я просто не соединила в своей голове эти две простые вещи:
если обновление пропсов должно приводить к ре-рендеру, тогда это должно работать и с
ref, не так ли?
Сейчас я читалю Advanced React от Nadia Makarevich, и автор пишет, что одно из главных заблуждений:
Миф про ре-рендеры: Компонент перерисовывается, когда изменились пропсы.
React обновляется, когда происходит обновление стейта. И в этом случае, он
перерендерит все вложенные компоненты, изменились их пропсы или нет.
В контексте ре-рендеров, изменились пропсы компонента или нет важно только в одном случае:
если этот компонент обернут в HOC React.memo.
И всё! Нужно поменять способ мышления — перенести фокус с изменения пропсов на
анализ изменений стейта.
P.S. Сама я для константных объектов / массивов предпочитаю создавать constants.ts и объявлять
их там, но это дело личного вкуса и никак не связано с ре-рендерами.
В наборе кат для изучения паттернов попалась одна про Higher-Order Component(HOC) в React —
truncate paragraph with HOC in React.
Плюс недавно разбиралась с паттерном Декоратор, и примером применения для React как раз считается HOC.
И вот я задумалась: а действительно ли хоки актуальны?
Чаще всего я вижу хоки, когда работаю с кодом, написанным 3-4 года назад, но в новом коде
я практически их не вижу. Опять же, в старой документации React есть целая страница,
посвященная HOC и примерам использования,
а вот в новой классной документации ничего нет.
Кажется, что кастомные хуки вытеснили хоки. Что такого особенного ты можешь сделать с HOC, что не можешь,
просто написав кастомный хук?
Лично я предпочитаю кастомные хуки или даже классы с вынесенной в них бизнес-логикой. Для меня это более
очевидный и поддерживаемый подход.
От моего внимания как-то ускользнуло, что в React можно передавать функцию-callback как ref для элемента, а не только объект,
созданный с помощью useRef.
const scroller = (node:HTMLDivElement) => {
if (!node) return;
node.scrollIntoView({ behavior: "smooth" });
};
// somewhere in component
<divref={scroller} />
Как это работает:
Когда элемент добавляется в DOM, React вызывает функцию-callback и передает в неё DOM-узел как аргумент.
Когда элемент удаляется из DOM, React вызывает эту функцию с аргументом null.
Callback также вызывается каждый раз, когда передается новая функция (например, при каждом рендере, если она определена как обычная функция).
Немного о задаче, которую сегодня решила на работе.
У нас есть таблица с фильтрами, которая выводит список визитов в офисы.
Некоторые визиты требуют дополнительного внимания менеджеров, например:
выдан пропуск посетителю, визит закончился, а пропуск не вернули.
Задача была подсветить такие визиты в таблице и дать возможность менеджерам
легко отфильтровать их. Я добавила подсветку для строк, а над таблицей - баннер с кнопкой
“Показать проблемые” (условно). Я также добавила функциональность, чтобы фильтр по проблемным визитам не сохранялся
(мы запоминаем выбранные фильтры пользователя). Всё это работало - пока не появились
проблемы с фильтрами.
Большая часть фильтров у нас - это дропдауны со списком опций. На это ветке
эти дропдауны стали медленно работать, и после 3-4 быстрых кликов страница ломалась.
В консоли появлялись ошибки о “Maximum update depth exceeded” и рекомендации проверить
useEffects и их зависимости.
Стек вызовов указывал на компонент дропдауна из библиотеки компонентов нашей компании.
Что я стала делать
Я начала с попытки понять, что происходит и где может быть проблема. Посмотрела изменения
в коде. Сложности добавляло то, что в этой же ветке я стала использовать код из другого
пакета в нашей монорепе (я использую npm workspaces), и изменений было много.
Естественно, я попыталась отлаживаться. Однако было слишком много вызовов, компонент
дропдауна выглядел нормально и не было в нем useEffects.
Я погуглила и спросила Claude. Время шло. Я решила перейти к одному
из моих любимых методов отладки - удалению частей кода.
Для начала я хотела понять, не из-за ли нового пакета возникла проблема.
Создала новую ветку от develop и перенесла минимальные изменения туда.
Фильтры всё еще работали отвратительно и ломались.
Затем я удалила части кода, касающиеся фичи - запросы и обработку данных.
Проблема осталась.
Я уже была готова сдаться, но решила удалить весь код фичи - и вдруг проблема исчезла.
Наконец, я нашла проблему: div, который оборачивал таблицу и баннер над ней.
Когда я заменила его на фрагмент, проблема исчезла.