无法访问下一个服务器操作的值 | Next.js 13.4

ric*_*rn1 10 validation reactjs next.js react-server-components next.js13

我很好奇我们如何能够访问 Next.js 13 的 alpha 版本服务器操作的返回值。这是Next 团队的文档供参考。

假设我有以下示例服务器操作来执行输入验证:

async function validation(formData: FormData) { // needs "formData" or else errors
    'use server';
    
    // Get the data from the submitted form
    const data = formData?.get("str");
    
    // Sanitize the data and put it into a "str" const
    const str = JSON.stringify(data)
        .replace(/['"]+/g, '')
    
    // Self explanatory
    if (!str) {
        return { error: "String is Required" }
    } 

    else if (!(/^[a-zA-Z0-9]{8}$/).test(str)) {
        return { error : "Invalid String Format" }
    }

    // Using next/navigation, redirect to another dynamic page
    // if the data is validated
    
    await redirect(`/scan-code=${str}`)
    return null;
    }
Run Code Online (Sandbox Code Playgroud)

该动作与一个简单的形式挂钩:

<form action={validation}>
    <label>
        {error && error}
    </label>
    <input 
        placeholder="Enter String Here"
        required 
        name="str"
    />
    <button type="submit">
        Submit
    </button>
</form>
Run Code Online (Sandbox Code Playgroud)

在 Remix 中,这将是使用操作和 useFetcher() 挂钩的非常简单的情况,并且该操作本质上看起来是相同的,除了在组件本身中,您可以使用类似的东西

const fetcher = useFetcher()

进而

const error = fetcher.data?.error

并通过类似的方式处理 UI 中的错误

<label style={{ color: error ? 'red' : 'black' }}>
{error && error}
</label>
Run Code Online (Sandbox Code Playgroud)

但是,我实际上只是返回错误或重定向到正确的页面,而不是能够检索错误值并根据它们是否存在来呈现它们,因此 UI 中没有错误反馈。

当首先尝试使用操作时,还会出现类型错误,并且异步函数的承诺与元素上的操作属性的预期类型不匹配,该元素显然是一个字符串。

( TS2322: Type '(formData: FormData) => Promise<{ error: string; } | null>' is not assignable to type 'string')

我很好奇我在这里做错了什么,以及 Next 的服务器操作的 RemixuseFetcher()useActionData()钩子的替代方案是什么,因为我觉得在上下文、本地存储或数据库中保留错误将是一个不必要的步骤。

任何和所有的帮助将不胜感激。谢谢你!

rav*_*ius 1

根据此GitHub 讨论中非常简洁的回复,您只能使用客户端呈现的表单来执行此操作。就我而言,我有一个服务器渲染的页面。该页面定义了一个内联服务器操作,然后将该操作传递给客户端呈现的表单。客户端渲染的表单用另一个async函数包装服务器操作,然后将新的包装函数传递给form['action']prop。

// app/whatever/new/client-rendered-form.tsx

"use client";

import { useState } from "react";

export default ClientRenderedForm({
  serverAction
}: {
  serverAction: (data: FormData) => Promise<unknown>
}) {
  const [error, setError] = useState<unknown>();

  return (
    <form
      action={async (data: FormData) => {
        try {
          await serverAction(data);
        } catch (e: unknown) {
          console.error(e);

          setError(e);
        }
      }}
    >
      <label>
        <span>Name</span>
        <input name="name" />
      </label>

      <button type="submit">pull the lever!</button>
    </form>
  );
}
Run Code Online (Sandbox Code Playgroud)
// app/whatever/new/page.tsx

import { redirect } from "next/navigation";

import ClientRenderedForm from "./client-rendered-form";

export default WhateverNewPage() {
  const serverAction = async (data: FormData) => {
    "use server";

    // i don't know why the 'use server' directive is required,
    // but it is. this file is already executed on the server
    //
    // this function runs on the server. you may process the data
    // however you'd like
    //
    // if this function throws, the client wrapper will catch
    //
    // when you are finished, you likely need to execute a
    // `redirect()`. i don't know if the `return` keyword
    // is required

    return redirect("/somewhere/else");
  };

  return (
    <div className="fancy-layout">
      <ClientRenderedForm {...{ serverAction }} />
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

我希望这有帮助!这个新范例还处于早期阶段,因此文档尚未填写。