Вопрос или проблема
Next.js как передать ‘setSomeState’ дочерним компонентам
Я пытаюсь создать приложение с блоком чата, который не обновляется при смене страницы. В дочерних страницах содержатся подсказки, чтобы избавить пользователя от необходимости вводить общие ответы. Все мои страницы находятся в директории /board, где есть страница верхнего уровня под названием ‘BoardContainer.tsx’:
'use client'
import React, { ReactElement, ReactNode } from 'react';
import { useState } from 'react';
import { usePathname } from 'next/navigation';
import ChatBlock from '../components/ChatBlock/ChatBlock';
import AboutPage from '../dashboard/about/background/page';
interface BoardContainerProps {
children: ReactNode;
}
const BoardContainer: React.FC<BoardContainerProps> = ({ children }) => {
const [selectedPrompt, setSelectedPrompt] = useState('');
const pathname = usePathname();
const sendPrompt = (prompt: string) => {
setSelectedPrompt(prompt);
}
const childrenWithProps = React.Children.map(children, child => {
console.dir(child);
if (React.isValidElement(child)) {
return React.cloneElement(child as ReactElement<any>, { sendPrompt });
}
console.dir(child);
return child;
});
console.dir(pathname);
return (
<>
<>
{ pathname === '/board/about' ? <AboutPage sendPrompt={sendPrompt} /> : <PageContent /> }
{/* Это работает, но не подходит для более чем 2 дочерних страниц */}
{/* childrenWithProps */}
{/* это тоже не работает */}
{/* children */}
<>
<>
<ChatBlock
prompt={selectedPrompt}
/>
</>
</>
)
}
export default BoardContainer;
Я нашел обходной путь, используя usePathname и фактически жестко кодируя значение дочернего элемента и передавая функцию sendPrompt, но это громоздко. Я пробовал использовать React.cloneElement, но либо использую это неправильно, либо что-то не понимаю. Как я могу передать функцию sendPrompt через { children }?
Ответ или решение
Чтобы передать функцию sendPrompt
дочерним компонентам в вашем приложении на Next.js, вы можете использовать метод React.Children.map
в сочетании с React.cloneElement
, как вы уже пытались. Однако важно корректно управлять типами и убедиться, что дочерние компоненты обрабатывают переданные им пропсы.
Ваш первоначальный подход почти правильный, но он нуждается в некоторых корректировках. Давайте посмотрим на решенную версию вашего кода:
'use client';
import React, { ReactElement, ReactNode, FC } from 'react';
import { useState } from 'react';
import { usePathname } from 'next/navigation';
import ChatBlock from '../components/ChatBlock/ChatBlock';
import AboutPage from '../dashboard/about/background/page';
interface BoardContainerProps {
children: ReactNode;
}
const BoardContainer: FC<BoardContainerProps> = ({ children }) => {
const [selectedPrompt, setSelectedPrompt] = useState('');
const pathname = usePathname();
const sendPrompt = (prompt: string) => {
setSelectedPrompt(prompt);
};
// Пробрасываем функции через дочерние элементы
const childrenWithProps = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { sendPrompt });
}
return child;
});
return (
<>
<>
{pathname === '/board/about' ? (
<AboutPage sendPrompt={sendPrompt} />
) : (
childrenWithProps
)}
</>
<ChatBlock prompt={selectedPrompt} />
</>
);
};
export default BoardContainer;
Объяснение кода:
-
Проверка типа Child: Мы используем
React.Children.map
для обхода всех дочерних элементов.React.isValidElement
обеспечивает, что мы работаем только с валидными React-элементами перед тем, как пытаться клонировать их. -
Клонирование элемента: Используем
React.cloneElement
, чтобы передатьsendPrompt
как пропс каждому дочернему элементу. Это позволяет дочерним компонентам вызыватьsendPrompt
и обновлять состояние родительского компонента. - Условие для рендеринга дочерних элементов: Если путь равен
/board/about
, отображается компонентAboutPage
, иsendPrompt
передается ему в качестве пропса. В противном случае отображаются все дочерние элементы, которые теперь имеют доступ к функцииsendPrompt
.
Примечания:
-
Убедитесь, что дочерние компоненты, которые будут использовать
sendPrompt
, действительно настроены на его использование. Например, в дочернем компоненте вы можете получатьsendPrompt
из пропсов и вызывать его при необходимости:const ChildComponent = ({ sendPrompt }) => { const handleClick = () => { sendPrompt('Ваш текст сообщения'); }; return <button onClick={handleClick}>Отправить</button>; };
Эта структура компонента позволит вам легко добавлять новые дочерние элементы и управлять функцией sendPrompt
без необходимости дублирования кода или использования жёстких ссылок на пути.