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

React useMutation配置onError后onMutate出现TypeScript类型错误(乐观更新)

解决React Query useMutation乐观更新的TypeScript类型错误

问题代码

import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useRef } from "react";
import { Todo } from "./hooks/useTodos";
import axios from "axios";

interface AddTodoContext {
  previousTodo: Todo[];
}

const TodoForm = () => {
  const queryClient = useQueryClient();

  const addTodo = useMutation<Todo, Error, Todo, AddTodoContext>({
    mutationFn: (todo: Todo) =>
      axios
        .post<Todo>("https://jsonplaceholder.typicode.com/todos", todo)
        .then((res) => res.data),

    onMutate: (newTodo: Todo) => {
      const previousTodos = queryClient.getQueryData<Todo[]>(["todos"]) || [];

      queryClient.setQueryData<Todo[]>(["todos"], (todos) => [
        newTodo,
        ...(todos || []),
      ]);
      if (ref.current) ref.current.value = "";

      return { previousTodos };
    },

    onSuccess: (savedTodo, newTodo) => {
      queryClient.setQueryData<Todo[]>(["todos"], (todos) =>
        todos?.map((todo) => (todo === newTodo ? savedTodo : todo))
      );
    },

    onError: (error, newTodo, context) => {
      if (!context) return;
      queryClient.setQueryData<Todo[]>(["todos"], context.previousTodo);
    },
  });

  const ref = useRef<HTMLInputElement>(null);

  return (
    <>
      {addTodo.error && (
        <div className="alert alert-danger">{addTodo.error.message}</div>
      )}
      <form
        className="row mb-3"
        onSubmit={(event) => {
          event.preventDefault();

          if (ref.current && ref.current?.value) {
            addTodo.mutate({
              id: 0,
              title: ref.current?.value,
              completed: false,
              userId: 1,
            });
          }
        }}
      >
        <div className="col">
          <input ref={ref} type="text" className="form-control" />
        </div>
        <div className="col">
          <button className="btn btn-primary">Add</button>
        </div>
      </form>
    </>
  );
};

export default TodoForm;

报错信息

Type '(newTodo: Todo) => { previousTodos: Todo[]; }' is not assignable to type '(variables: Todo) => AddTodoContext | Promise<AddTodoContext | undefined> | undefined'.
  Type '{ previousTodos: Todo[]; }' is not assignable to type 'AddTodoContext | Promise<AddTodoContext | undefined> | undefined'.ts(2322)
queryClient-bm-z2rsD.d.ts(598, 5): The expected type comes from property 'onMutate' which is declared here on type 'UseMutationOptions<Todo, Error, Todo, AddTodoContext>'
(property) onMutate?: ((variables: Todo) => AddTodoContext | Promise<AddTodoContext | undefined> | undefined) | undefined

解决方案

类型错误的核心原因是**AddTodoContext接口的属性名和onMutate返回对象的属性名不匹配**:

  • 接口定义的是previousTodo(单数)
  • onMutate返回的是previousTodos(复数,多了个s)

只需要将两者的属性名统一即可,有两种修改方式:

方式一:修改AddTodoContext接口

将接口里的属性名改为和返回对象一致:

interface AddTodoContext {
  previousTodos: Todo[]; // 把previousTodo改为previousTodos
}

同时同步修改onError里的引用:

onError: (error, newTodo, context) => {
  if (!context) return;
  queryClient.setQueryData<Todo[]>(["todos"], context.previousTodos); // 这里也改成previousTodos
};

方式二:修改onMutate的返回对象

让返回对象的属性名和接口定义一致:

onMutate: (newTodo: Todo) => {
  // ...其他代码
  return { previousTodo: previousTodos }; // 把previousTodos改为previousTodo
};

两种方式任选其一,修改后TypeScript的类型检查就会通过,乐观更新的逻辑也能正常运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 10:25:36