In my previous post, we talked about how to replace some component lifecycle functions with useEffect and useReducer hooks, while making the resour...
For further actions, you may consider blocking this person and/or reporting abuse
dispatch
also need to be abortable...dispatch
can be aborted if you useredux-thunk
orredux-saga
- only applies to async actions though.for example:
to use it:
it's not limited to
fetch
oraxios
- you can use AbortSignal for many things, just be careful it might not throw error automatically when used outside of making requests.developer.mozilla.org/en-US/docs/W...
You are using fetch, is it working the same for axios or is there some change?
pretty much the same.
axios
can either use an AbortController or a CancelToken, check their examples here: axios-http.com/docs/cancellationYes I checked their doc, I have trouble cancelling one request via redux. Basically I have my axios in a service file, then I call the axios req in the action. In my component I have a useEffect which runs when require, and save data to api, and whenever I trigger the button, I would like the call to be cancelled. Any help on how to achieve this ? thanks
Here is my service file :
my action file
and my component
looks like you are calling
abort
already in the dispatch line instead of providing a signal. same to theonClick
handler. I would do this:it does not seem to work, idk why, is there a chance you can take a look at the service and action file that I posted, just to check if I have made any mistake thanks
could you be a bit more specific though 😅 a minimal example would be helpful.
the method explained in the post definitely works - you probably have something else going on in the app
yes the component seems fine, it is just that in my action and service file, I am not sure I set up the controller properly
I managed to make it work, thank you. I was wondering, how do I clear the abort controller once request cancelled ? thanks
awesome news. 😅
oh yea about that there are different methods. I think the abort controller should only be created before each request. one way of doing that is
useRef
ok but is there another method, because this one does not work for my use case thanks
This is what I have so far. I have a modal, this modal is saving data to a database, once I click on the abort, i cancelled the call (to save data) in the useEffect. Let 's say now the user exit the modal and decide some other time to go back on the modal to save its data again, the cancel signal is still on the route. How do I clear up the abort controller ?
Here is my service file :
my action file
and my component
in your example it's hard to tell what the issue is or what are you trying to do.
two things that's kinda obvious here:
useEffect
only fires once (when the component mounts) - the request is not made on demand, which seems to be what you are trying to do here. If you want the request to be made every time the modal is opened, you will need to make sure opening the modal (either rendering the modal, or changing the state) is tied to the effect/request.also, when you say "it doesn't work" - please elaborate why or how. there could be many reasons for broken code, without errors/logs/descriptions, it's impossible to tell what might be wrong by simply looking at a few lines of code.
Great article! However I don't see why you couldn't use the AbortController even if you're not using
fetch
. You'd just check for theabortController.signal.aborted
flag in whateverresolve
orreject
block you get from thePromise
.Thanks - yes that's possible - however in that case the
AbortController
instance basically acts like a boolean flag. 😅it can be helpful in certain scenarios though. if you are working with a lot of Promises - I'd recommend checking out npmjs.com/package/cancelable-promise
Whoah, that library looks like just what I was looking for. Thanks!
I am dispatching a thunk call in my useEffect. Now, if I use isCancelled flag as suggested, I want to understand how is it actually preventing state update when my component is unmounted. now, thunk is calling the api in the background, and sets the redux state in the background. now, when happens? When is
if(!isCancelled)
inside the useEffect is being checked?In your case using the flag is not going to work.
If you are using
fetch
to make requests you can use AbortController (just provide the signal to the thunk action)in your request handler's
catch
block, make sure checking if the error is an "AbortError"There are many different implementations for making requests with thunk actions - could you show some code?
my component file has this -
in a diff .js file, I have written the thunk function. which uses axios internally -
Looks like you can just introduce a second parameter to your
thunk
function:now in your component you should provide the cancel token to thunk
so, inside my api function, I am creating new token on every request - if it's the same request though, then it cancels the prev token/req, and re-generate the token and handling everything related to axios inside this function.
So, I will need to change it and basically create token inside useEffect.
Will you be able to redirect me to React docs/github/etc where they suggest this solution? Thank you
No, the token should be created in your
useEffect
call. A new token is created for every new "effect".cancel
/abort
is called whenever the effect re-fires (e.g. when the parameters changed, or when the component unmounts), the cleanup function is called, cancelling the previous request - in your API function you should check if a request has been aborted in yourcatch
block and handle it accordingly.some helpful articles:
reactjs.org/blog/2015/12/16/ismoun...
reactjs.org/docs/hooks-effect.html
github.com/axios/axios#cancellation
But what about other api request especially the event driven ones such as post, patch, put, and delete? I followed this tutorial using axios instead of fetch but i still get the same warning/error. Axios's CancelToken is the same but I can't seem to make a post, patch, put and delete request without re-starting the browser, the initial list component that was fetch in component did mount was unmounted during "isLoading" phase, need help with my code here:
const postRequest = useCallback(() => {
let source = axios.CancelToken.source();
const postData = async (entry) => {
dispatch(loading());
try {
const response = await axios.post(
'/list',
{
cancelToken: source.token,
},
entry
);
dispatch(processingRequest(response.data));
} catch (err) {
if (axios.isCancel(err)) {
dispatch(handlingError);
}
}
};
}, []);
Hi - I looked at your program, it doesn't work because the request is never cleaned up. My post talks about automatically clean up requests with
useEffect
indeed it might not be so easy to work with for POST/PUT, etc, or requests that only fire on user action (not via an effect).Your code, uses
useCallback
which is just a simple memoizer. thereturn
at the end won't clean it up for you. We can rewrite it to a function creator that returns a function that automatically cleans up its previous request when called again:to use it:
this function will only allow one request being made at the same time - it doesn't cancel the request for you when the component unmounts though, to do that, we should move
cancelToken
to aref
, here's a possible implementation for that:now we don't need to "make" a new requester anymore, to use it we can call it directly with the new "entry". This
postList
function automatically cancels its previous request when called again, and if there are any pending requests, they will be canceled when the component unmounts.I followed the new one but i couldn't make any request for the first approach, It's a simple mern stack, I'm using context api + useReducer:
Actions:
Reducer:
here's my custom hook for all 5 types of api request:
I'm using the custom hooks as values to the context api, here's the main component where the list component is unmounted during the "isLoading" phase, as you can see the get request is inside the useEffect:
Here's one of the modals for event driven requests like post:
Hi - that's a lot of code - i took a quick look, one thing I probably didn't explain well with my first example is that the
makeRequest
method returns a function that makes requests when called. (i know it's a bit confusing)in your example, your
getRequest
orpostRequest
methods are factories - to use them, you have to do something like:please try to follow my 2nd example as it cleans up the requests when component unmounts. I'd recommend trying to start small, don't try to get everything working in one go, instead, try to focus on only 1 method, and get it to work correctly (and tested) first.
The new
postRequest
could look something like this:Please note this method is very different from the first one - to use it, do something like this in the component:
there were a couple of other issues with your code, for exmaple,
axios.post
takes configuration as the 3rd parameter, not the 2nd; and in yourcatch
blocksaxios.isCancel
means the request was canceled (instead of encountered an error) - usually we want to handle error when the request was NOT canceled.Anyways, try to get a single request working properly first before trying to optimize or generalize your use case, don't worry about separating functionality or abstraction at this stage.
Hi sorry for the late reply, I followed your suggestions and here's what my app can do:
Here's my updated custom hook that has all the factory requests:
I tried negating the: axios.isCancel(err) but to no avail, here's my api request codes.
GET Request:
POST Request:
DELETE Request:
hi @paolo - sorry I just saw this. Could you setup a github repo or add me to your existing one? my github handle is @pallymore
alternatively could you set this up on codesandbox.io ? it'll be easier to read/write code there, thanks!
Thanks so much, I added you on github :)
i have implement axios way, but the problem that it cancel all requests immediately and not after leaving the component.
is that a syntax error ?
import { useEffect, useState } from "react";
import Api from "#shared/Api";
import { CancelToken, isCancel } from "axios";
const GetReqhandler = (path) => {
}
export default GetReqhandler;
hmm this looks correct to me - could you setup an code example on codesandbox?
Also this is a custom hook, right?
Yeah, this is a custom hook, here is an example of it's implementation,
i am reformatting code into an old react project, so i tried to implement it to see if it's efficient for the performance, so i can change it in the whole the project (Notice : when i implement it, i didn't change the other normal requests. It might be because of that, i don't know)
Sorry for my late response.
I don't see any obvious errors with the implementation. maybe I'm not getting your question right - are you saying
cancel
is called right away when the component is still mounted? that shouldn't happen since you provided[]
touseEffect
which means it'll only run once and the clean up function is only called when the component unmounts (similar tocomponentDidMount
+componentWillUnmount
).I made something similar to your first example and it works.
codesandbox.io/s/fast-cdn-j37lb?fi...
one quick things though: hook names should start with
use
- instead ofGetReqHandler
you probably want to rename it douseGetReqHandler
.Another thing to note is if you have hot module reloading - your page might unmount and re-mount the component which will cause cancellations - but if you are not doing that, I don't think the problem is here, you might want to check if your code works properly if you change it to a class component.
Yea i thnik this is the problem.
instead of codesandbox here is the project on gitlab
React Project
the useGetReques used under home/abonne/abonne.js
and it's parent is home/home_container.js
if you could suggest a solution to solve this problem using hooks or i any kind of parameters.
Sorry again - would you mind adding me to that project? my gitlab handle is @pallymore
Thanks
I have solve it, i had to change the root component of routes to class instead of fuctional component and it solved the problem
Could you help me how to cancel request using userMeno with dispatch I get this error ( Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. )
const [stateApp, dispatch] = useReducer(
(stateApp, action) => {
switch (action.type) {
case "SET_APP":
return {
...stateApp,
data: { ...action.payload },
loading: false,
success:true,
error: undefined
};
case "SET_ERROR":
return {
...stateApp,
error: { ...action.payload },
loading:false,
data: {},
success:false
};
default:
return stateApp
}
},
{
data: {},
error: undefined,
loading: true,
success: false
},
)
Hi - I'd recommend not to use
useMemo
in this case - it should be reserved for memoizing results from expensive operations - not functions!useMemo
should be avoided unless you absolutely need it for performance reasons (in real life this doesn't happen very often).Back to the topic - in your case your method is not cancelled when the component unmounts - the right solution would be creating a cancel token (or AbortController if you are using
fetch
) and provide the signal to yourauthentication.get
method - and then incatch
make sure you check for AbortErrors. This depends on the implementation of yourauthentication.get
method.Alternatively - you can do something like this:
please note the request is not aborted unless you provide the
signal
to yourfetch
call. this code here only prevents react from performing state updates.This is a wonderfully flexible solution! Thanks a lot! I understood useReducer much better. But I got a problem. I carefully copied the code and I am getting Maximum update depth exceeded error. This error occurs due to useEffect dependencies on url. If I set [ ] empty array in dependencies all ok, I get my remote data. What am I missing?
useFetch.js
And use it in App.js
sorry for the late response - in your code you are declaring dependency on
options
- which is an object, since react only does shallow comparison, if your options are not memoized, it will triggeruseEffect
on every render (and cancel previous requests).to solve this - I'd recommend declaring an explicit list of simple values that you support in
options
(instead of accepting everything)also I'd remove the default value
options = {}
- since react will create a fresh object{}
on every render, causinguseEffect
to fire unnecessarily.how does this work with axios?
axios
has this thing calledCancelToken
: github.com/axios/axios#cancellationit is very similar to
AbortController
😄however I would not use
axios
in the front end though.fetch
is very easy to work with - if you want some of theaxios
' default behaviors (throw on 4xx/5xx, returns data by default) you can easily wrapfetch
in your own helper function to do that.Why are you not recommending using axios?
Because
fetch
is already pretty good. I'm not againstaxios
- if you know what you are doing. For any new devs I'd highly recommend learning all the basic DOM APIs and utilities instead of trying to find a third party library for everything.Cool, yeah, best tip, learn all basic DOM API, I'm currently doing this one.
Tbh, this is a very underrated tip but very helpful in the long run.
A complete guide here: wareboss.com/react-hook-clean-up-u...
How does this works with redux and redux-saga? is it advisable to clean up on every fetch?
If you are using
takeLatest
-redux-saga
already cancels the effect for you. If you want to abort the request as well, try this:If you want to cancel sagas manually, check out their cancellation documentation:
redux-saga.js.org/docs/advanced/Ta...
link: github.com/redux-saga/redux-saga/i...
Cool but how do you test it?
Something like this:
I think that might work - however it seems to be testing implementation details - which might be ok if your hook does nothing when aborted.
For testing with
fetch
I usually use something likesinon
's fakeServer. You can intercept requests but not respond to it - unmount the component (or anything that triggers an abort) and check if corresponding side effects are firing (or not firing - e.g. no actions were dispatched)Cool, I used abortController in useEffect, it worked. Thank you!
abcd