redux-toolkit icon indicating copy to clipboard operation
redux-toolkit copied to clipboard

RTKQ: Mutation gets stuck in 'Pending' despite rejection from the server.

Open aloysb opened this issue 3 years ago • 17 comments

Hi,

On multiple places, since I've updated our package the mutation is stuck on 'Pending' even when an error is thrown.

See screenshot and code below. The server returns 422, but the mutation doesn't get rejected.

It works with fulfilled mutation, but not rejected ones. I have the issue in multiple places in my codebase and had to revert back to handling the state manually.

Any clues? Thank you!

// Mutation
  verifyEmail: builder.mutation({
      query: ({ key, email }) => ({
        url: `membership/validate-email/${key}/${email}`,
        method: "PUT",
      }),
      
 // In my component

   const [
    verifyEmailMutation,
    { isLoading, isError, isSuccess, isUninitialized },
  ] = useVerifyEmailMutation();
  
  <button onClick={() => { isUninitialized && verifyEmailMutation({ key, email }); } > Button </button>
Screen Shot 2021-08-03 at 10 59 28

aloysb avatar Aug 03 '21 09:08 aloysb

Could you try to create a reproduction for this?

phryneas avatar Aug 03 '21 11:08 phryneas

@phryneas ,

Thank you so much for such awesome package. I have used it inside my current app and I am literally enjoying this .

One thing, I have been struggling with is the Pending state of mutation.

I have a scenario where I have been using RTK mutation to fetch data from server on demand. However, when ever I get a 500 error, it gets logged to my middleware

export const rtkQueryErrorLogger: Middleware = (api: MiddlewareAPI) => (next) => (action) => {
   console.log(isRejected(action));
   if (isRejectedWithValue(action)) {
        console.warn('We got a rejected action!')
        console.log({ ...action?.payload, data: '', })
        ShowCustomError(action.payload);
    }
    return next(action);
}

but, the mutation is always in Pending state, not even giving any error state.

As for now, I can see, that I will have to switch to the asyncThunk logic, and managing the state manually.

Can you please provide me some reference to manually trigger the rejection of mutation. So, I can get through it.

Thanks

rana944 avatar Nov 29 '21 08:11 rana944

@rana944

Could you try to create a reproduction for this?

this applies here as well. without any code showing this in a reproducable way I have no idea what is going on.

phryneas avatar Nov 29 '21 09:11 phryneas

@phryneas ,

Thank you so much for such quick response.

I have managed to resolve this issue by using custom base query (Axios Base query).

This is how, I have achieved this,

export const testApi = createApi({
    reducerPath: 'testApi',
    baseQuery: axiosBaseQuery(),
    endpoints: (builder) => ({
        getTopTestItems: builder.mutation<TopTestItem[], TopTestParams>({
            query: (data) => getInterceptor(URLS.TEST1, data, data.token),
        }),
        getTestItems: builder.mutation<TestItem[], TestParams>({
            query: (data) => getInterceptor(URLS.TEST2, data, data.token),
            transformResponse: (response: { data: TestItem[] }) => response.data
        }),
    }),
});

This is the code my GET request interceptor

const getInterceptor = (url: string, params: any, token?: string | undefined) => {
    return {
        url,
        method: 'GET',
        params,
        headers: {
            'Content-Type': 'application/json;charset=UTF-8',
            'Authorization': `JWT ${token}`,
        },
    }
}

This is the code for my custom base query

const axiosInstance = axios.create({
    baseURL: BASE_URL,
});

const axiosBaseQuery = (): BaseQueryFn<any> => async (
    requestOptions,
) => {
    try {

        const result = await axiossInstance({
            ...requestOptions,
        });
    
        return { data: result.data };
    } catch (e: any) {
        return { error: e?.message }
    }
}

This is how I am calling the above mutation,

const [
    getTopTestItems,
    {
        isLoading,
        data,
    }
] = useGetTopTestItemsQuery();

useEffect(() => {
    const testParams = { };
    getTopTestItems({ ...testParams });
}, [])

Thank you

rana944 avatar Nov 29 '21 10:11 rana944

@rana944 I need a CodeSandbox, git repository or something else that shows me the problem, not the workaround. If there is a problem, we need to find and fix it in RTK-Query, so that not everyone has to go for the workarounds you have here.

But without a reproduction I have no idea what to look for.

phryneas avatar Nov 29 '21 10:11 phryneas

Hi, I'm currently facing the similar issue and have a reproducible repo here: https://github.com/polaroidkidd/librephotos-frontend/tree/feat/migrate-ts--auth-state

I defined the request here and I'm using it on the signup page here.

The successful query goes through as advertised, but when I try to sign up with the same user name twice, the api correctly returns 400 correctly and the rejected response is logged out here, but the status remains pending

To get talking to the api you'd have to clone https://github.com/LibrePhotos/librephotos-docker, rename librephotos.env to .env and run docker-compose up -d.

I'm fairly sure I've misconfigured something here somehwere since RTK-Query is still new to me, but seeing as there's already a ticket for it and you mention needing a reproducible example, I thought I'd throw my hat in the ring.

polaroidkidd avatar Apr 02 '22 22:04 polaroidkidd

I did some further digging and found a bug in my zod declarations, which stopped the mutation from entering the fulfilled state.

It wasn't related to this after all.

polaroidkidd avatar Apr 03 '22 20:04 polaroidkidd

Got the same issue when i use GET request with Lazy Query and got 500 error from server, status stacked in "Pending" position...

nofear144 avatar Jun 13 '22 14:06 nofear144

@nofear144 I can only quote myself from this issue:

without a reproduction I have no idea what to look for.

phryneas avatar Jun 13 '22 14:06 phryneas

Got the same issue when I use POST request and got success status from the server but the status was stuck in the "Pending" position.

amandeep-qasource avatar Oct 05 '22 07:10 amandeep-qasource

Cool. Could someone at this point maybe even just try to create a reproduction of this instead of saying "I have this problem too"?

Because obviously we have never encountered this. We cannot help you otherwise.

phryneas avatar Oct 05 '22 07:10 phryneas

Cool. Could someone at this point maybe even just try to create a reproduction of this instead of saying "I have this problem too"?

Because obviously we have never encountered this. We cannot help you otherwise.

Thank you so much for your quick response!

I am calling a POST API twice in a component using mutation For first-time mutation call on button click, it is working fine, getting response perfectly fine, but when the same API is called again from button click event it is pausing at the pending status and no output is coming.

amandeep-qasource avatar Oct 05 '22 08:10 amandeep-qasource

I need a reproduction with code that I can run on my machine and see it for myself.

That could be a GitHub repo, a CodeSandbix or a Replay recording.

phryneas avatar Oct 05 '22 09:10 phryneas

Same problem. I know that you're looking for a snippet to reproduce the error. But actually it seems to be random (I tried to create a code sandbox for you but the error disappeared there..)

Till now, I've tried to change the API endpoint name and the tag and the issues seems to disappear for a while, but after some time it appears again. Could be something related to the cache?

v-x2 avatar Oct 08 '22 09:10 v-x2

@v-x2 I really can't tell. I'd at least need a replay recording of this happening once.

phryneas avatar Oct 08 '22 09:10 phryneas

I encountered a similar problem, but it was probably just misusage from my side. For me the request kept hanging as pending because I had a request argument that depended on the current time, which of course changed with every execution.

Something like this:

  const since =
    listState === 'closed'
      ? subDays(new Date(), 90)
      : listState === 'new'
      ? subDays(new Date(), 7)
      : undefined;

  const {
    currentData,
    error,
    isFetching,
  } = useGetDataQuery({
    since: since?.toISOString(),
  });

I resolved this by using the current date without time as a reference point, so since stays constant. Since this was probably just my fault, I did not take the time to set up a repository. However I thought I should just quickly share my finding.

If it is helpful to you, I can set up a repository reproducing this behavior as soon as I have the time.

dominikberse avatar Oct 12 '22 10:10 dominikberse

@dominikberse no need for a repo, that's really just misuse and nothing we could debug or fix on our side (as it could be intended just as well). But thanks a lot for sharing, maybe it helps someone here!

phryneas avatar Oct 12 '22 10:10 phryneas

i encountered similar issues but after removing the middleware am using, it worked as expected.

/** Log a warning and show a notification! **/ const rtkQuerySuccessLogger = (api: MiddlewareAPI) => (next: any) => (action: { meta: { arg: { type: string } } }) => { if (isFulfilled(action)) { if (action.meta.arg.type === 'mutation') { return message.success('Action Successful!'); } } return next(action); };

Geemarn avatar Oct 26 '22 06:10 Geemarn

@Geemarn yes, that middleware will stop the actions from ever reaching the reducer.

/** Log a warning and show a notification! **/
const rtkQuerySuccessLogger =
(api: MiddlewareAPI) => (next: any) => (action: { meta: { arg: { type: string } } }) => {
if (isFulfilled(action)) {
if (action.meta.arg.type === 'mutation') {
+ next(action)
return message.success('Action Successful!');
}
}
return next(action);
};

You always have to call next(action) or the action will never reach the store.

Generally I'd recommend you look at the Listener Middleware for something like that

phryneas avatar Oct 26 '22 06:10 phryneas

yeah, thanks..just observed it now

Geemarn avatar Oct 26 '22 07:10 Geemarn

I have the same issue and provide a codeSandBox for you guys.

but I'm not sure if you can actually make a request to my server feel free to check out the code. I must say I actually test this code and in my localhost, I am able to see a status freeze in pending for my getCandles query but getInstrument works as expected.

Abolfazl2647 avatar Dec 11 '22 14:12 Abolfazl2647

@Abolfazl2647 your code just makes a new request every time the component rerenders.

Every time the component rerenders, new Date().toISOString() will be a different value, so now RTK Query goes ahead and starts another request. That way, you will never finish loading.

phryneas avatar Dec 11 '22 14:12 phryneas

@phryneas thank you, I should see that sooner. sorry to bother you.😖

Abolfazl2647 avatar Dec 11 '22 15:12 Abolfazl2647

codeSandBoxExample Mutation is stuck in pending status if get error in reducer added by addMatcher

axinvd avatar Dec 21 '22 13:12 axinvd

@axinvd : yeah, that's to be expected. If an error is thrown in the reducer, then the state never actually gets updated.

markerikson avatar Dec 21 '22 16:12 markerikson

i am getting the same error in Post Mutation , but the Args i passed is Successfully originated inside originalArgs!! what should i do now

const [respoInfo, response] = useEventUserMutation()
console.log(response,'responssdsdsd')

useEffect(() => {
        (async () => {
            if (event_id !== undefined || null) {
                await  respoInfo(event_id)
            }           
        })()
        console.log(response,'responssdsdsd')
},[event_id])

console.log(response, 'reponse')
useEffect(() => {
    if (response.isSuccess === true) {
        console.log(response,'Done ')
    }
},[response])

//mutation code

eventUser: builder.mutation({ query: (event_id) => ({ url:'eventuser', method: 'POST', body: event_id, headers: { 'Content-Type':'application/json' }
}) }),

originalArgs : 39 //response args

//working in postMan

vasimshaikhh avatar May 02 '23 12:05 vasimshaikhh

Hello! Why this issue is closed and how did you resolve this? Today is 2023 and I faced the same problem when I used RTK Query.

azad-source avatar Jun 10 '23 14:06 azad-source

@azad-source as you can read here, they had manual code in a reducer that was erroring. You likely have a different problem. Please open a new issue and provide a reproduction or a replay recording of the problem.

phryneas avatar Jun 10 '23 15:06 phryneas

@phryneas thanks! I'll try to hide my reducer's slice and I'll check if the problem goes away or not. May be I have the same reason. :)

azad-source avatar Jun 10 '23 16:06 azad-source

Hello everyone, I ran into this problem because I was doing a mutation call into a popup react component that is unmounted just after the mutation call :

const [doMutation, res] = useSomeMutation();
  /* some code...*/
() => {
 doMutation({...});
 //this method call close the popup so react unmount the component
 onValidate();
}

Adding 'await' before the mutation call helps resolve this because the "res" variable is updated before the component is unmounted:

async () => {
 await doMutation({...});
 //this method call close the popup so react unmount the component
 onValidate();
}

Hope it'll help someone :)

pmoradei avatar Jul 19 '23 07:07 pmoradei