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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:46:04