You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 14 Server Actions如何触发客户端回调?无需useEffect方案

在Next.js 14 Server Actions中实现回调式状态处理

针对你想要替代useFormState+useEffect的优化需求,以下是几种更简洁、复用性更强的方案:

方案1:封装自定义Hook复用回调逻辑

把状态处理和回调逻辑封装成可复用的自定义Hook,避免重复编写useEffect,同时让组件代码更简洁。

步骤1:定义结构化返回的Server Action

// app/actions.ts
'use server';

export async function registerUser(formData: FormData) {
  const email = formData.get('email') as string;
  
  // 模拟邮箱已存在的场景
  if (email === 'existing@example.com') {
    return {
      success: false,
      message: '该邮箱已被注册',
      errorCode: 'EMAIL_EXISTS'
    };
  }

  // 正常注册逻辑(省略数据库操作)
  return {
    success: true,
    message: '注册成功',
    data: { email }
  };
}

步骤2:编写自定义Hook

// app/hooks/useFormActionWithCallbacks.ts
'use client';

import { useFormState, useEffect } from 'react';

type ActionState<T = unknown> = {
  success: boolean;
  message: string;
  errorCode?: string;
  data?: T;
};

export function useFormActionWithCallbacks<T>(
  action: (...args: any[]) => Promise<ActionState<T>>,
  initialState: ActionState<T>,
  callbacks: {
    onSuccess?: (data: T, message: string) => void;
    onError?: (errorCode: string, message: string) => void;
  }
) {
  const [state, formAction] = useFormState(action, initialState);

  useEffect(() => {
    // 初始状态不触发回调
    if (state === initialState) return;

    if (state.success) {
      callbacks.onSuccess?.(state.data!, state.message);
    } else if (state.errorCode) {
      callbacks.onError?.(state.errorCode, state.message);
    }
  }, [state, initialState, callbacks]);

  return [state, formAction] as const;
}

步骤3:在组件中使用

// app/register/page.tsx
'use client';

import { registerUser } from '../actions';
import { useFormActionWithCallbacks } from '../hooks/useFormActionWithCallbacks';
import { toast } from 'your-toast-library'; // 替换为你使用的Toast库

const initialState = {
  success: false,
  message: '',
  errorCode: undefined,
  data: undefined
};

export default function RegisterPage() {
  const [state, formAction] = useFormActionWithCallbacks(
    registerUser,
    initialState,
    {
      onSuccess: (data, message) => {
        toast.success(message);
        // 注册成功后的额外操作,比如跳转首页
        window.location.href = '/';
      },
      onError: (errorCode, message) => {
        if (errorCode === 'EMAIL_EXISTS') {
          toast.error(message);
        }
      }
    }
  );

  return (
    <form action={formAction}>
      <label htmlFor="email">邮箱</label>
      <input type="email" id="email" name="email" required />
      <button type="submit">注册</button>
    </form>
  );
}

方案2:组件内精准依赖的useEffect优化

如果不想额外编写自定义Hook,可以通过缩小useEffect的依赖范围,避免不必要的重触发:

'use client';

import { useFormState, useEffect } from 'react';
import { registerUser } from '../actions';
import { toast } from 'your-toast-library';

const initialState = { success: false, message: '', errorCode: undefined };

export default function RegisterPage() {
  const [state, formAction] = useFormState(registerUser, initialState);

  useEffect(() => {
    // 跳过初始渲染
    if (state === initialState) return;

    if (state.success) {
      toast.success(state.message);
    } else if (state.errorCode) {
      toast.error(state.message);
    }

    // 可选:重置状态,避免页面重新渲染时重复触发回调
    // setState(initialState);
  }, [state.success, state.errorCode, state.message]);

  return (
    <form action={formAction}>
      <label htmlFor="email">邮箱</label>
      <input type="email" id="email" name="email" required />
      <button type="submit">注册</button>
    </form>
  );
}

方案3:结合useFormStatus处理加载+回调

如果需要同时管理提交加载状态,可以把useFormStatus和回调逻辑分离,让组件职责更清晰:

'use client';

import { useFormState, useFormStatus, useEffect } from 'react';
import { registerUser } from '../actions';
import { toast } from 'your-toast-library';

const initialState = { success: false, message: '', errorCode: undefined };

// 独立的提交按钮组件,处理加载状态
function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '注册中...' : '注册'}
    </button>
  );
}

export default function RegisterPage() {
  const [state, formAction] = useFormState(registerUser, initialState);

  useEffect(() => {
    if (state === initialState) return;

    if (state.success) {
      toast.success(state.message);
    } else if (state.errorCode) {
      toast.error(state.message);
    }
  }, [state]);

  return (
    <form action={formAction}>
      <label htmlFor="email">邮箱</label>
      <input type="email" id="email" name="email" required />
      <SubmitButton />
    </form>
  );
}

内容的提问来源于stack exchange,提问作者OlusanyaAyomide

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 08:23:20