I have a simple ToDos app that uses RTK Query to query a NodeJS backend, and update and cache state. It's working fine, interacting with the backend and updating state as expected. I decided to add snackbar notifications to display messages coming from the backend during queries and mutations, but I'm getting a warning in the console when the snackbar renders
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.
I found that this is because the snackbar is updating state and rendering at the same time as RTK Query is updating the cache and rerendering my ToDos page -- in the case of deleting a Todo for example.
I'm using the code below (todosDelete.tsx) to render a link, handle the delete action and render a snackbar.
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
Here's my slice for my ToDos:
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
The providesTags parameter on the fetchTodos builder query, in conjunction with the invalidatesTags parameters on the mutations, updates the cache and forces a rerender of the ToDos page. The warning occurs when the snackbar is updating state and rendering at the same moment, causing the warning above. If I comment out the providesTags/invalidatesTags parameters, the ToDos page doesn't update, as I would expect, and the snackbar renders without the warning. My question is how to go about dealing with these parallel state changes to avoid the warning.
You are enqueueing updates as unintentional side-effects during a component render. Keep in mind that the entire function body of a React Function component is the "render method".
Move these into a
useEffecthook so they are issued as intentional side-effects.Example: