Snackbar与RTK Query并行更新引发状态过渡警告的解决方法
解决RTK Query与Snackbar并行状态更新导致的React警告
我开发了一个基于RTK Query对接NodeJS后端的简易ToDos应用,可正常与后端交互并更新缓存状态。添加Snackbar通知以展示查询/ mutation时的后端消息后,控制台出现如下警告:
todosDelete.tsx:14 Warning: Cannot update during an existing state transition (such as within
render). Render methods should be a pure function of props and state.
经排查,该警告因RTK Query更新缓存并重渲染ToDos页面(例如删除Todo时),与Snackbar更新状态的操作并行触发导致。
当前代码实现
todosDelete.tsx
import { isErrorWithMessage, isFetchBaseQueryError } from '../../helpers/errorTypeHelper' import { ToDo, useDeleteTodoMutation } from './todoApiSlice' import { useSnackbar } from 'notistack' const TodosDelete = (item: ToDo): JSX.Element => { const [deleteTodo, { isSuccess, isError, error, data}] = useDeleteTodoMutation() const { enqueueSnackbar } = useSnackbar(); // handle success and error messages if (isSuccess) { enqueueSnackbar(data.message, { variant: 'info' }) } if (isError) { if (isFetchBaseQueryError(error)) { // you can access all properties of `FetchBaseQueryError` here const errMsg = 'error' in error ? error.error : JSON.stringify(error.data) enqueueSnackbar(errMsg, { variant: 'error' }) } else if (isErrorWithMessage(error)) { // you can access a string 'message' property here enqueueSnackbar(error.message, { variant: 'error' }) } } return <div onClick={() => deleteTodo(item)}>Delete</div> } export default TodosDelete
todoApiSlice.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { Dayjs } from 'dayjs' export type ToDo = { _id?: string | undefined // making id optional because it isn't passed in the form, but added in the action handler title: string body: string email: string date: Dayjs | string } interface ToDoState { list: ToDo[] } const initialState: ToDoState = { list: [] } const todoApiSlice = createApi({ reducerPath: 'todoApi', baseQuery: fetchBaseQuery({ // baseUrl: '/backend' baseUrl: 'http://localhost:3001' // for running outside of docker // prepareHeaders (headers) { // // headers.set('x-api-key', apiKey) // need to send the auth token here // return headers // } }), tagTypes: ['Todos'], // set the tags for the cache endpoints(builder) { return { fetchTodos: builder.query({ query: () => '/todos', providesTags: ['Todos'] // provide tags and revalidate the cache }), fetchSingleTodo: builder.query({ query: (id: string) => ({ url: `/todos/${id}`, method: 'GET' }) }), createTodo: builder.mutation({ query: (todo: ToDo) => ({ url: '/todos/', method: 'POST', body: todo }), invalidatesTags: ['Todos'] // invalidate the cache - reload new data after creating }), updateTodo: builder.mutation({ query: (todo: ToDo) => ({ url: '/todos/', method: 'PUT', body: todo }), invalidatesTags: ['Todos'] // invalidate the cache - reload new data after updating }), deleteTodo: builder.mutation({ query: (todo: ToDo) => ({ url: '/todos/', method: 'DELETE', body: todo }), invalidatesTags: ['Todos'] // invalidate the cache - reload new data after deleting }) } } }) export default todoApiSlice export const { useFetchTodosQuery, useFetchSingleTodoQuery, useCreateTodoMutation, useUpdateTodoMutation, useDeleteTodoMutation } = todoApiSlice
解决方案
核心问题是在组件渲染阶段直接调用enqueueSnackbar触发状态更新,与RTK Query缓存更新引发的重渲染冲突。只需将Snackbar的触发逻辑移到useEffect副作用中,确保不在渲染周期内执行状态更新:
修改后的todosDelete.tsx
import { isErrorWithMessage, isFetchBaseQueryError } from '../../helpers/errorTypeHelper' import { ToDo, useDeleteTodoMutation } from './todoApiSlice' import { useSnackbar } from 'notistack' import { useEffect } from 'react' const TodosDelete = (item: ToDo): JSX.Element => { const [deleteTodo, { isSuccess, isError, error, data}] = useDeleteTodoMutation() const { enqueueSnackbar } = useSnackbar(); // 用useEffect监听mutation状态变化,避免在渲染阶段更新状态 useEffect(() => { if (isSuccess && data) { enqueueSnackbar(data.message, { variant: 'info' }) } if (isError && error) { let errMsg = 'Unknown error' if (isFetchBaseQueryError(error)) { errMsg = 'error' in error ? error.error : JSON.stringify(error.data) } else if (isErrorWithMessage(error)) { errMsg = error.message } enqueueSnackbar(errMsg, { variant: 'error' }) } }, [isSuccess, isError, error, data, enqueueSnackbar]) return <div onClick={() => deleteTodo(item)}>Delete</div> } export default TodosDelete
修改说明
- 引入
useEffect,将原渲染阶段执行的Snackbar逻辑移到副作用中,确保状态更新在渲染完成后执行 - 依赖数组包含所有相关状态和方法,确保只有当mutation状态变化时才触发Snackbar
- 增加空值判断,避免潜在的空引用错误
- RTK Query的
providesTags和invalidatesTags无需修改,缓存更新逻辑保持正常
内容的提问来源于stack exchange,提问作者jblack
相关产品推荐
相关产品推荐

