Как передать состояние от дочернего компонента к родительскому в Next.js?

В моем проекте next.js 14 мне нужно передать состояние из дочернего компонента в родительский компонент.

На данный момент я объявил состояние в родительском компоненте и передал его дочерним компонентам. (таким образом делая все на стороне клиента родительского компонента)

Но я хочу, когда это возможно, сохранить серверный компонент. Вот минимальный воспроизводимый пример того, что я пытаюсь сделать:

У меня состояние isFocused. На панели навигации есть кнопка для ее переключения. Когда он включен. Весь сайт просто,

<Navbar/>
<FocusedPage />

Если не включено,

<Navbar/>
<Hero />
<FocusedPage />
...

Теперь, визуализировать компонент <Hero /> или нет, мне нужно знать, является ли isFocusedtrue или false.

Итак, я храню его в родительском компоненте клиента. Таким образом, <Hero /> также становится клиентским компонентом.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
186
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, вы должны знать, что использование клиентских компонентов не является плохой практикой, они существуют по какой-то причине, и эти причины упомянуты в Документах NextJS:

  • Интерактивность: клиентские компоненты могут использовать прослушиватели состояний, эффектов и событий, то есть они могут предоставлять немедленную обратную связь пользователю и обновлять пользовательский интерфейс.
  • API-интерфейсы браузера. Клиентские компоненты имеют доступ к API-интерфейсам браузера, таким как геолокация или localStorage.

Также, по словам Ли Робинсона из Vercel:

Клиентские компоненты не являются ни отключением, ни отговоркой. Вы можете рассматривать серверные компоненты как дополнение к существующей модели. Я понимаю желание использовать серверные компоненты для многих целей (неплохо!), но также совершенно нормально использовать клиентские компоненты для определенных целей.

Однако иногда (как в вашей ситуации) вам необходимо сохранить все серверные компоненты, кроме интерактивной части, в качестве клиентского компонента.

Подъем состояния из дочернего клиентского компонента (из-за прослушивателя событий onClick) на родительский серверный компонент невозможен, и он выдаст ошибку времени выполнения:

Необработанная ошибка времени выполнения

Ошибка: функции нельзя передать непосредственно клиентским компонентам, если вы явно не предоставите их, пометив «использовать сервер».

Решение

Самый распространенный способ «повысить состояние» в NextJS — это использовать URLSearchParams вы будете использовать его во многих ситуациях, особенно когда хотите передать данные с клиента на сервер, но это не единственный вариант использования.

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

В настоящее время у вас есть четыре компонента и один page.tsx, все компоненты будут серверными, кроме Navbar.tsx, вы поместите свое состояние в Navbar.tsx вместо Home.tsx, потому что этот компонент отвечает за изменение состояния, а также будет иметь прослушиватель событий для изменения состояния и добавьте параметр запроса в URL-адрес, чтобы использовать его позже в компонентах сервера.

Навбар.tsx

'use client'; // make sure it's a client component

import { useRouter } from 'next/navigation';
import { useState, useEffect } from 'react';
const Navbar = () => {
  // bring the state here
  const [focusMode, setFocusMode] = useState(false);
  const router = useRouter();

  // useEffect will append the query param whenever the state changes
  useEffect(() => {
    router.push(`?focusMode=${focusMode}`);
  }, [focusMode]);

  return (
    <nav className = "flex items-center justify-between border-b-2 border-gray-400 px-5 py-5">
      <a className = "text-2xl font-bold tracking-tighter" href = "/">
        Next.js
      </a>
      <div>
        <a href = "/" className = "mr-5 underline">
          Home
        </a>
        <button
          className = "rounded-md border bg-gray-700 p-2 font-semibold text-gray-100 hover:bg-gray-500"
          onClick = {() => setFocusMode((prevIsFocused) => !prevIsFocused)}
        >
          Toggle Focus Button
        </button>
      </div>
    </nav>
  );
};

export default Navbar;

По умолчанию page.tsx получает searchParams, который представляет собой объект, содержащий все параметры запроса, вы можете передать его home.tsx:

страница.tsx

import Home from '@/components/Home';

interface IProps {
  params: {};
  searchParams: {
    focusMode: string;
  };
}
export default function Root({ searchParams }: IProps) {
  return (
    <>
      <Home focusMode = {JSON.parse(searchParams?.focusMode ?? false)} />
    </>
  );
}

JSON.parse необходимо преобразовать true/false из string в boolean, поскольку все, что вы получаете из URL-адреса, — это string.

searchParams?.focusMode ?? false необходим, потому что при первом рендере у вас его не будет searchParams, поэтому он вернется undefined.

Наконец, вот как вы Home.tsx будете выглядеть:

Home.tsx

import Navbar from '@/components/Navbar';
import Hero from './Hero';
import Focused from '@/components/Focused';

interface IProps {
  focusMode: boolean;
}
function Home({ focusMode }: IProps) {
  return (
    <main className = "text-center">
      <Navbar />
      {!focusMode && <Hero />}
      <Focused focusMode = {focusMode} />
    </main>
  );
}

export default Home;

все остальные файлы остались как были.

Бонус

Если вы хотите убедиться, что все компоненты являются серверными, попробуйте console.info(window) в любом из них, и вы увидите ошибку (кроме Navbar.tsx, потому что это клиентский компонент):

Посмотрите рабочий пример на StackBlitz

Все работает нормально. Спасибо за совет.

Nusab19 21.06.2024 10:08

Другие вопросы по теме