Next.js:提交表单时状态尚未准备好

问题描述 投票:0回答:1

我有一个

onSubmit
函数,大致如下所示:

  const [isPending, startTransition] = useTransition();
  const [urls, setUrls] = useState<string[]>([]);
  const { edgestore } = useEdgeStore();

  const onSubmit = async (values: z.infer<typeof formSchema>) => {
    try {
      await Promise.all(
        filesToAdd.map(async (addedFileState) => {
            const res = await edgestore.publicFiles.upload({
              file: addedFileState.file as File,
            });

            setUrls((prev) => [...prev, res.url]);
        }),
      )
        .then(() => {
          startTransition(() => {
            createRecord({ imagesUrls: urls, name: values.name })
          });
        })
    } catch {
      toast.error("Something went wrong");
    }
  };

我遇到的问题是,在

try
块中,我成功地将文件上传到 Edgestore 存储桶,并将正确接收的
res.url
分配给
urls
状态,当函数命中
.then
块时state 尚未准备好,
urls
仍然是一个空数组,在数据库中插入一条不完整的记录。

如何设法仅在状态就绪后才尝试在数据库中创建记录?

我尝试查看是否可以利用依赖项数组中具有

useEffect
urls
,但这使得 useEffect 触发的次数与我上传的文件一样多(因为
Promise.all
)。

我该如何解决这个问题?

reactjs next.js state
1个回答
0
投票

您面临的问题是批处理。

什么是批处理?

React.js 从版本 18 开始提供了一个新概念,称为批处理。这意味着它将一些状态更新推迟到下一次重新渲染,以避免不必要的重新渲染。

在事件处理程序(如您的情况)和

setTimeout
回调中自动反应批处理状态。简而言之,当您在事件处理程序中设置状态时(
onClick
onSubmit
等),React 不会重新渲染(更新状态),直到事件处理程序结束。

示例

const [state, setState] = useState(1);

const onSubmit = async (values: ValuesType) => {
  await doSomething(values);

  // Currently, state === 1
  
  setState(2)
  
  // Still, state === 1, not 2
}

阅读有关 React 中批处理的更多信息:

你的例子怎么样?

   const onSubmit = async (values: z.infer<typeof formSchema>) => {
    try {
      await Promise.all(
        filesToAdd.map(async (addedFileState) => {
            const res = await edgestore.publicFiles.upload({
              file: addedFileState.file as File,
            });

            setUrls((prev) => [...prev, res.url]);
        }),
      )
        .then(() => {
          startTransition(() => {
            createRecord({ imagesUrls: urls, name: values.name })
          });
        })
    } catch {
      toast.error("Something went wrong");
    }
  };

下一行位于事件处理程序和 for 循环中:

setUrls((prev) => [...prev, res.url]);

如果 React 重新渲染组件,它将销毁整个事件处理程序。想象一下,每次重新渲染都会顺利上传第一个文件,然后当它到达上一行时,React 重新渲染组件并从头开始执行事件处理程序,直到到达同一行然后重新渲染,如此等等。 ..这将是一个无限循环。

它的作用是什么?

让我们简化您的功能

const [numbers, setNumbers] = useState<number[]>([])
const onSubmit = () => {
  // here, numbers = []
  setNumbers(nums => [...nums, 1])
 
  // At the moment, numbers still equal = []
  // But React remembers that when it finish the event handler
  // It will run this function `nums => [...nums, 1]` on the numbers state
 
  setNumbers(nums => [...nums, 2])

  // What do you think `numbers` equal?
  // Yes, numbers = [], and react will re-rember to run this function
  // nums => [...nums, 2]

  console.log(numbers);
  // What is the output?
  // empty array.
}

此事件处理程序完成执行后,React 将在下一次重新渲染时运行此事件

let numbers = [];

const batchesUpdates = [
  function (nums) { return [...nums, 1] },
  function (nums) { return [...nums, 2] },
]

batchesUpdates.forEatch(func => {
  numbers = func(numbers)
})

明白了吗?

如何处理您的用例?

  1. 您可以在事件处理程序中声明内部变量,并使用它
 const onSubmit = async (values: z.infer<typeof formSchema>) => {

    // internal variable
    const updloadedFilesUrls = [];

    try {
      await Promise.all(
        filesToAdd.map(async (addedFileState) => {
            const res = await edgestore.publicFiles.upload({
              file: addedFileState.file as File,
            });
          
            // update the internal variable
            updloadedFilesUrls.push(res.url)
        }),
      )
        .then(() => {
          startTransition(() => {

            // use the internal variable
            createRecord({ imagesUrls: updloadedFilesUrls, name: values.name })
          });

          // Update the state
          setUrls(urls => [...urls, ...updloadedFilesUrls])

        })
    } catch {
      toast.error("Something went wrong");
    }
  };
© www.soinside.com 2019 - 2024. All rights reserved.