Как работает useState в Next.js и является ли состояние глобальным или локальным для страницы
Что проверяют
Понимаете ли вы, где живёт состояние React в приложении с серверным рендерингом и что с ним происходит при навигации. Следом обычно спрашивают, можно ли вызвать useState в серверном компоненте и как поделиться состоянием между страницами.
Как отвечать
useState в Next.js работает так же, как в React: состояние принадлежит экземпляру компонента в дереве, а не странице и не приложению. Два одинаковых компонента на экране держат два независимых значения. Начальное значение используется только при монтировании, дорогую инициализацию передают функцией useState(() => compute()).
Особенности App Router:
- только клиентские компоненты: в серверном компоненте хуки недоступны, файл с
useStateначинается с"use client"; - SSR и гидратация: на сервере компонент рендерится с начальным значением, заново на каждый запрос, затем React гидратирует разметку, и дальше состояние живёт только в браузере. Значение, разное на сервере и клиенте (
localStorage,Date.now(),Math.random()), даёт ошибку гидратации, его читают вuseEffect; - навигация:
layout.tsxне перемонтируется при переходах между дочерними маршрутами, и состояние в нём сохраняется. Страница при уходе обычно размонтируется, но в Next.js 16 с включённымcacheComponentsпредыдущие маршруты скрываются через<Activity>и сохраняют состояние при возврате; - перезагрузка: полная перезагрузка сбрасывает всё.
Общее состояние поднимают в провайдер контекста в layout, выносят в Zustand или Redux Toolkit, а то, что должно переживать перезагрузку, кладут в URL через searchParams.
Где спотыкаются
Говорят, что состояние инициализируется при каждом рендере. Только при монтировании, и поэтому useState(props.value) не обновится, когда проп поменяется.
Заводят глобальный стор переменной на уровне модуля. На сервере модуль общий для всех запросов, и данные одного пользователя могут попасть в рендер другого. Стор создают на каждый запрос.
Считают, что состояние страницы гарантированно сбросится при уходе с неё. В layout и при включённом cacheComponents оно сохраняется, и сброс делают явно через key или очистку в эффекте.