← Назад к вопросам
Как при использовании action в одном компоненте получить те же самые данные в другом компоненте?
2.0 Middle🔥 111 комментариев
#React
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как при использовании action в одном компоненте получить те же данные в другом
Server Actions в Next.js позволяют компонентам вызывать код на сервере, но для обмена данными между компонентами нужны дополнительные механизмы. Есть несколько проверенных подходов для синхронизации данных.
Проблема
// ComponentA.tsx - вызывает server action
"use client";
async function handleSave(formData) {
const result = await updateQuestionAction(formData);
// result содержит новые данные
// Как передать их в ComponentB?
}
// ComponentB.tsx - нужны те же данные
export function ComponentB() {
// Как получить данные из ComponentA?
}
Решение 1: React Context API
Идеально для компонентов в одном поддереве.
// contexts/DataContext.tsx
"use client";
import { createContext, useContext, useState, ReactNode } from "react";
interface DataContextType {
questionData: any | null;
setQuestionData: (data: any) => void;
}
const DataContext = createContext<DataContextType | undefined>(undefined);
export function DataProvider({ children }: { children: ReactNode }) {
const [questionData, setQuestionData] = useState(null);
return (
<DataContext.Provider value={{ questionData, setQuestionData }}>
{children}
</DataContext.Provider>
);
}
export function useQuestionData() {
const context = useContext(DataContext);
if (!context) {
throw new Error("useQuestionData must be within DataProvider");
}
return context;
}
// app/layout.tsx - оборачиваем провайдер
export default function RootLayout({ children }) {
return (
<html>
<body>
<DataProvider>{children}</DataProvider>
</body>
</html>
);
}
// ComponentA.tsx - сохраняет данные
"use client";
import { useQuestionData } from "@/contexts/DataContext";
import { updateQuestionAction } from "@/app/actions";
export function ComponentA() {
const { setQuestionData } = useQuestionData();
async function handleSubmit(formData: FormData) {
const result = await updateQuestionAction(formData);
// Сохраняем результат в контекст - другие компоненты его видят
setQuestionData(result);
}
return (
<form action={handleSubmit}>
<input name="title" placeholder="Question title" required />
<button type="submit">Save</button>
</form>
);
}
// ComponentB.tsx - читает данные
"use client";
import { useQuestionData } from "@/contexts/DataContext";
export function ComponentB() {
const { questionData } = useQuestionData();
if (!questionData) {
return <div>No data yet</div>;
}
return (
<div>
<h2>{questionData.title}</h2>
<p>{questionData.description}</p>
</div>
);
}
Решение 2: Zustand (для глобального состояния)
Легче чем Context, без нескольких провайдеров.
// lib/store/questionStore.ts
import { create } from "zustand";
interface QuestionState {
questionData: any | null;
setQuestionData: (data: any) => void;
clearData: () => void;
}
export const useQuestionStore = create<QuestionState>((set) => ({
questionData: null,
setQuestionData: (data) => set({ questionData: data }),
clearData: () => set({ questionData: null })
}));
// ComponentA.tsx
"use client";
import { useQuestionStore } from "@/lib/store/questionStore";
import { updateQuestionAction } from "@/app/actions";
export function ComponentA() {
const setQuestionData = useQuestionStore((state) => state.setQuestionData);
async function handleSubmit(formData: FormData) {
const result = await updateQuestionAction(formData);
setQuestionData(result); // Сохраняем в глобальное состояние
}
return (
<form action={handleSubmit}>
<input name="title" required />
<button type="submit">Save</button>
</form>
);
}
// ComponentB.tsx
"use client";
import { useQuestionStore } from "@/lib/store/questionStore";
export function ComponentB() {
const questionData = useQuestionStore((state) => state.questionData);
if (!questionData) return <div>Select a question</div>;
return (
<div>
<h2>{questionData.title}</h2>
<p>{questionData.description}</p>
</div>
);
}
Решение 3: Через URL параметры (Server-Driven)
Данные на сервере, компоненты всегда получают актуальные данные.
// app/actions.ts
"use server";
import { redirect } from "next/navigation";
export async function updateQuestionAction(formData: FormData) {
const title = formData.get("title");
// Сохраняем в БД
const result = await db.questions.create({ title });
// Перенаправляем с ID новой записи
redirect(`/questions/${result.id}`);
}
// app/questions/[id]/page.tsx
export async function Page({ params }) {
// На сервере загружаем актуальные данные
const questionData = await db.questions.findById(params.id);
return (
<div>
<ComponentA />
<ComponentB data={questionData} />
</div>
);
}
// ComponentB.tsx - получает данные через props
interface Props {
data: any;
}
export function ComponentB({ data }: Props) {
return (
<div>
<h2>{data.title}</h2>
<p>{data.description}</p>
</div>
);
}
Решение 4: Через пропсы (для простых случаев)
Для компонентов которые живут в одном месте.
// page.tsx
"use client";
import { useState } from "react";
export default function Page() {
const [sharedData, setSharedData] = useState(null);
return (
<div>
{/* ComponentA передаёт данные родителю */}
<ComponentA onDataSave={setSharedData} />
{/* ComponentB получает данные через пропсы */}
<ComponentB data={sharedData} />
</div>
);
}
// ComponentA.tsx
"use client";
import { updateQuestionAction } from "@/app/actions";
interface Props {
onDataSave: (data: any) => void;
}
export function ComponentA({ onDataSave }: Props) {
async function handleSubmit(formData: FormData) {
const result = await updateQuestionAction(formData);
onDataSave(result); // Передаём результат в родителя
}
return (
<form action={handleSubmit}>
<input name="title" required />
<button type="submit">Save</button>
</form>
);
}
// ComponentB.tsx
interface Props {
data: any | null;
}
export function ComponentB({ data }: Props) {
if (!data) return <div>No data</div>;
return (
<div>
<h2>{data.title}</h2>
</div>
);
}
Решение 5: useTransition + revalidateTag
Данные автоматически обновляются на сервере.
// app/actions.ts
"use server";
import { revalidateTag } from "next/cache";
export async function updateQuestionAction(formData: FormData) {
const title = formData.get("title");
const result = await db.questions.create({ title });
// Инвалидируем кэш - ComponentB автоматически получит новые данные
revalidateTag("questions");
return result;
}
// ComponentA.tsx
"use client";
import { useTransition } from "react";
import { updateQuestionAction } from "@/app/actions";
export function ComponentA() {
const [isPending, startTransition] = useTransition();
function handleSubmit(formData: FormData) {
startTransition(async () => {
await updateQuestionAction(formData);
// После этого ComponentB автоматически обновится
});
}
return (
<form action={handleSubmit}>
<input name="title" required />
<button type="submit" disabled={isPending}>
{isPending ? "Saving..." : "Save"}
</button>
</form>
);
}
// ComponentB.tsx - серверный компонент
async function ComponentB() {
// Данные кэшируются и переполучаются после revalidateTag
const data = await fetchQuestions();
return (
<div>
<h2>{data.title}</h2>
</div>
);
}
Сравнение подходов
| Подход | Когда использовать | Плюсы | Минусы |
|---|---|---|---|
| Context | Компоненты в одном поддереве | Встроено в React | Пробрасывание пропсов |
| Zustand | Глобальное состояние | Простой, быстрый | Ещё одна библиотека |
| URL params | Server-centric App | Shareable URLs, SEO | Видимо в браузере |
| Props | Простые случаи | Никакие зависимости | Не масштабируется |
| revalidateTag | Автообновление данных | Данные всегда свежие | Требует кэширования |
Рекомендация для PrepBro
- Локальное состояние в одной странице -> Props
- Глобальное состояние (вопросы, профиль) -> Zustand
- Большие изменения данных -> revalidateTag + useTransition
- Критичные данные -> URL параметры (server-side fetch)
Итог: Выбери подход в зависимости от масштаба приложения и размещения компонентов. Для PrepBro рекомендую Zustand для вопросов и профиля, остальное через пропсы и server actions.