← Назад к вопросам

Как при использовании 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 paramsServer-centric AppShareable URLs, SEOВидимо в браузере
PropsПростые случаиНикакие зависимостиНе масштабируется
revalidateTagАвтообновление данныхДанные всегда свежиеТребует кэширования

Рекомендация для PrepBro

  • Локальное состояние в одной странице -> Props
  • Глобальное состояние (вопросы, профиль) -> Zustand
  • Большие изменения данных -> revalidateTag + useTransition
  • Критичные данные -> URL параметры (server-side fetch)

Итог: Выбери подход в зависимости от масштаба приложения и размещения компонентов. Для PrepBro рекомендую Zustand для вопросов и профиля, остальное через пропсы и server actions.