Redux RTK optimistic update causes Immer error - reactjs

I've followed the docs (and some other posts) about RTK optimistic updates but I can't seem to get my case to work. Here is my createReply mutation:
createReply: builder.mutation<IPost, any>({
query({...body}) {
return {
url: PostPath,
method: 'POST',
body: body,
};
},
async onQueryStarted({...body}, {dispatch, queryFulfilled}) {
const patchResult = dispatch(
resourcesApi.util.updateQueryData('getRepliesById', body.parent, (draft) => {
console.log(draft);
Object.assign(draft, body)
})
)
try {
await queryFulfilled
} catch {
console.log("query was fulfilled");
console.log(patchResult);
// patchResult.undo()
}
}
}),
and this is the getRepliesById query that it's referencing:
getRepliesById: builder.query<IPost, string>({
query: (id: string) => `${PostPath}/${id}/replies`,
}),
When I try to use this mutation, the POST works fine, but I'm given this error:
[Immer] Immer only supports setting array indices and the 'length' property.
(The error call stack ends with onQueryStarted).
Where here am I going against Immer rules?
Edit; Here is body:
const postObj = {
content: reply,
author: user._id,
parent: _parentId,
visibility: {scope: 'public'},
media: [],
ats: [],
kind: 'comment',
};
createReply(postObj);

It seems that data is an array and now you are working with it as if it were an object. An array can only have number-indexed properties and your Object.assign seems to add other properties than that?

Related

RTK Query optimistic update returning empty Proxy object

I have two api slices, one is userApiSlice and one is teamsApiSlice. In userApiSlice I'm trying to change a user's team and using optimistic update for better ux in case of slow network or anything. here is my userApiSlice where I'm trying to make optimistic update:
changeUsersTeam: builder.mutation({
query: (data) => ({
url: `/user/change-team/${data.id}`,
method: "put",
body: data.body,
}),
invalidatesTags: ["Team"],
async onQueryStarted(data, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
teamsApiSlice.util.updateQueryData(
// #ts-ignore
"getTeamById",
data.id.toString(),
(draft) => {
console.log("test from api slice", draft);
if (data.body.team !== "substitudes") {
draft.subMembers = [];
draft.activeMembers.push(draft.studentId);
} else {
draft.activeMembers = [];
draft.subMembers.push(draft.studentId);
}
}
)
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
},
}),
but in console log I'm getting this empty proxy object:
Proxy: { <target>:null,<handler>:null }
so the optimistic update is not working

Redux RTK Query - Lazy Query

I simply want to use RTK to run an API request as part of an onSuccess function and await the response. I was doing this in Axios no problem but I'm trying to replace my Axios calls with RTK and having trouble using the LazyQuery.
I'm getting an exception
useLazyQuery is not a function or its return value is not iterable
My API:
const linkAPI = overweightRTK.injectEndpoints({
tagTypes: ['Link'],
endpoints: (builder) => ({
createLink: builder.mutation({
query: (body) => ({
url: `/links/`,
method: 'POST',
body,
invalidatesTags: ['Link']
})
})
}),
overrideExisting: false
});
export const { useCreateLinkMutation} = linkAPI;
My component:
const [trigger, result, lastPromiseInfo] = OverweightRTK.endpoints.createLink.useLazyQuery()
const onSuccess = async (data) => {
const createLinkRequest = {
payload: data
};
trigger({ createLinkRequest }, { skip: !isSubscriber })
.unwrap()
.then(res => {
if (res.status === 200) {
setSuccessMessage('Success, your account linked');
}
})
.catch((error) => console.log(error));
};
I figured it out. Apparently for mutation endpoints you don't need to use the lazyQuery(). Mutation queries by default return a similar response as lazyQuery and already return the tuple containing the trigger.
Working code: (This replaces the first line in my component code, nothing else needed to be changed)
const [trigger, result, lastPromiseInfo] = useCreateLinkMutation();
Useful docs on the subject: https://redux-toolkit.js.org/rtk-query/usage/mutations

I am getting error while updateQueryData in rtk query, how do I fix this?

I am new to react query and tried many tutorials still I am not logging anything and nothing happens after the following code, cannot even log new data, It doesnt feel like there is any error. POST is working perfectly.
addCategory: builder.mutation({
query: (body) => ({
method: "POST",
url: apiLinks.categoryPUT,
body: body,
}),
async onQueryStarted(body, { dispatch, queryFulfilled }) {
try {
const res = await queryFulfilled;
const id = _.get(res, "data.data.InsertedID", Date.now());
dispatch(
categoryApi.util.updateQueryData(
"getAllCategory",
undefined,
(data) => {
const newBody = { id, ...body };
console.log({ data, newBody }); //nothing shows here
data.push(newBody);
}
)
);
} catch (error) {
console.log({ error }); // nothing logs here
}
},
}),
problem was I used this: useGetAllCategoryQuery("getAllCat"); to fetch data.
useGetAllCategoryQuery("getAllCat");
so it should be:
categoryApi.util.updateQueryData(
"getAllCategory",
"getAllCat", //change here
seems its a unique identifier for cache data

the first request doesn't send to the database in working with RTK query

In my project I'd like to increase and decrease amount of the foods listed . but after push the "More" button in the first request doesn't change anything in database but after push it again in second request just increse it once (after this refresh the page you'll figure it out). actually I don't know why the first request doesn't counted .
Demo :https://laughing-jang-201dc1.netlify.app/ .
Github : https://github.com/alibidjandy/burger
this is the main RTK Query configuration :
https://github.com/alibidjandy/burger/blob/main/src/Api/apiSlice.ts
import { current } from "#reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import {
AllBurgerType,
BurgerType,
IngredientsType,
} from "../components/Layouts/burger/burgerSlice/burgerSlice";
export const burgerApi = createApi({
reducerPath: "burgerApi",
tagTypes: ["Ingredients"],
baseQuery: fetchBaseQuery({
baseUrl:
"https://burger-order-brown-default-rtdb.europe-west1.firebasedatabase.app",
}),
endpoints: (builder) => ({
getIngredients: builder.query<BurgerType, undefined>({
// query: () => `/.json`,
query: () => {
return { url: "/.json", method: "GET" };
},
providesTags: ["Ingredients"],
}),
editIngredients: builder.mutation({
query: (initialIngredients) => {
return { url: "/.json", method: "PATCH", body: initialIngredients };
},
invalidatesTags: ["Ingredients"],
}),
increase: builder.mutation({
query: ({ ing }) => {
return {
url: `/ingredients/${ing.id}/.json`,
method: "PATCH",
body: ing,
};
},
async onQueryStarted({ ing }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
burgerApi.util.updateQueryData(
"getIngredients",
undefined,
(draft) => {
console.log("increase");
// debugger;
const ingredient = draft.ingredients.find(
(ingredient) => ingredient.title === ing.title
);
if (ingredient) {
console.log(current(ingredient));
ingredient.Qty!++;
}
}
)
);
try {
await queryFulfilled;
} catch {
console.log("crashed");
patchResult.undo();
}
},
}),
decrease: builder.mutation({
query: ({ ing, indexIng }) => {
return {
url: `/ingredients/${indexIng}/.json`,
method: "POST",
body: ing,
};
},
async onQueryStarted({ ing, indexIng }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
burgerApi.util.updateQueryData(
"getIngredients",
undefined,
(draft) => {
console.log("decrese");
const ingredient = draft.ingredients[indexIng];
if (ingredient.Qty !== undefined && ingredient.Qty > 0) {
draft.totalCost = +(draft.totalCost -=
ingredient.cost!).toFixed(2);
ingredient.Qty--;
}
}
)
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
},
}),
}),
});
export const { editIngredients, getIngredients } = burgerApi.endpoints;
export const {
useGetIngredientsQuery,
useEditIngredientsMutation,
useIncreaseMutation,
useDecreaseMutation,
} = burgerApi;
Wait... I'm just re-reading your code. I think you might have a very wrong idea on what onQueryStarted and updateQueryData do. onQueryStarted runs after your request has been sent to the server. updateQueryData only updates your data on the client.
So you send the old client-side data to the server and then update it on the client side.
Yes, no wonder your data is always one step behind.
You need to send updated data to the server in the first place.
You would need to do something like
increase: builder.mutation({
query: ({ ing }) => {
return {
url: `/ingredients/${ing.id}/.json`,
method: "PATCH",
body: { ...ing, Qty: ing.Qty + 1 }
};
},
and no onQueryStarted at all.
Just use invalidatesTags to have the correct data refetched from the server afterwards.
Also, as I stumbled over that somewhere else in your code: you are generating an id using nanoid() and using that as key. Never do that. A key has to be stable, not be a different one on every render. That will always cause React to destroy the whole child DOM on every render and throw away the whole local state of all child components as well.

How Can I Destructure error from is RTK Query Mutation Hook?

I am using redux toolkit query for my data fetching API.
The problem is I am having is error handling redux toolkit returns an error property from the query hook. So the error that is returned from the server is an object with nested data and what trying to access it I get a typescript error when trying to access the data from the error object.
below is how I am declaring my mutation hook
const [updateProgram, {
isLoading: isUpdatingProgram,
error: updateProgramError
}] = useUpdateProgramMutation();
below is how I try to access the error in my code.
onSubmit = {
async(values, {
setSubmitting
}) => {
await updateProgram({ ...values,
id: 'programData.data._id'
});
refetch();
if (updateProgramError) {
enqueueSnackbar('Test message', {
variant: 'error'
});
console.log(updateProgramError?.data?.message);
}
setSubmitting(false);
}
}
now the typescript error I am getting is as below.
Not that I able to console.log(updateProgramError) and is the data in the console
Property 'data' does not exist on type 'FetchBaseQueryError | SerializedError'.
Property 'data' does not exist on type 'SerializedError'.
below is updateProgramError when I log is as a whole on the console
{
"status": 500,
"data": {
"status": "error",
"message": "Something went very wrong!"
}
}
below is how I have implemented useUpdateProgramMutation().
import { ndovuAPI } from './ndovuAPI';
export const ndovuProgramsAPI = ndovuAPI.injectEndpoints({
endpoints: builder => ({
getProgramById: builder.query({
query: (id: string) => `/programs/${id}`,
providesTags: ['Programs']
}),
getAllPrograms: builder.query({
query: queryParams => ({ url: `/programs/`, params: queryParams }),
providesTags: ['Programs']
}),
registerProgram: builder.mutation({
query: body => ({
url: '/programs',
method: 'POST',
body
}),
invalidatesTags: ['Programs']
}),
updateProgram: builder.mutation({
query: body => ({ url: `/programs/${body.id}`, method: 'PATCH', body }),
invalidatesTags: ['Programs']
})
}),
overrideExisting: true
});
// Export hooks for usage in functional components
export const { useGetProgramByIdQuery, useGetAllProgramsQuery, useUpdateProgramMutation } = ndovuProgramsAPI;
The code as you have it written will not work the way you want. You can't reference the error from the hook inside of an onSubmit handler like that. What you'll want to do is this:
const onSubmit = async (values) => {
try {
// unwrapping will cause data to resolve, or an error to be thrown, and will narrow the types
await updateProgram({
...values,
id: 'programData.data._id'
}).unwrap();
// refetch(); // you should most likely just use tag invalidation here instead of calling refetch
} catch (error) {
// error is going to be `unknown` so you'll want to use a typeguard like:
if (isMyKnownError(error)) { // add a typeguard like this
enqueueSnackbar('Test message', {
variant: 'error'
});
} else {
// You have some other error, handle it differently?
}
}
}
References:
https://redux-toolkit.js.org/rtk-query/usage/mutations#frequently-used-mutation-hook-return-values
From what I can infer from this TS error, the type of updateProgramError seems like an union of types 'FetchBaseQueryError', 'SerializedError' and possibly other types, which means that TS only assumes that you can safely access this data property after ensuring that data exists in the object.
In other words, TS does not know which of these types updateProgramError will be unless you do some checking to ensure that.
if (updateProgramError) {
enqueueSnackbar('Test message', {
variant: 'error'
});
if ('data' in updateProgramError) console.log(updateProgramError.data.message);
}

Resources