Hi all, this isn't a bug but I would appreciate some feedback.
I have a middleware that runs fetches for me, essentially I can dispatch an action like the following and it will run the fetch for me and then invoke the relevant callback.
{
type: 'FETCH_USER'
meta: {
method: 'GET'
endpoint: 'http://api.mydomain.com/user/1,
success: successCallbackFunction // (possibly an action creator to be dispatched).
error: errorCallbackFunction // (possibly an action creator to be dispatched).
}
Typically this sort of action is dispatched in a componentWillMount method to do an async fetch of the data the component requires.
This has been working well for me but recently I noticed a couple of bugs relating to this approach. If the order of execution is as follows:
The react documentation here states:
When fetching data asynchronously, use componentWillUnmount to cancel any outstanding requests before the component is unmounted.
As far as I am aware fetch has no abort() method like the jQuery ajax does as used in the example on the React documentation. On top of that, because the fetch is handled by my middleware I don't really have a reference to it in my component.
To mitigate the issue I have put logic in the reducers for the callbacks to only mutate the state if relevant conditions are true. This works well but it feels like unnecessary callbacks, reducers and fetches are happening and I worry about how this would be as the app grows.
Does anyone have any feedback or tips on this kind of approach and how you deal with cleaning up when a component unmounts.
Thanks!
There currently is no cancellation support in fetch() API although that may change in the future.
If you change your middleware to use XMLHttpRequest directly you can tweak it to return the { abort } method.
const myMiddleware = store => next => action => {
const xhr = // ...
const ready = new Promise((resolve, reject) => {
xhr.onload = () => { dispatch(...); resolve() }
xhr.onerror = () => { dispatch(...); reject(...) }
// ...
xhr.send()
})
const abort = xhr.abort.bind(xhr)
return { ready, abort }
}
Then your components will have access to it as a part of dispatch() return value:
const operation = this.props.dispatch(fetchStuff())
operation.ready.then(...)
operation.abort()
Awesome. Thanks @gaearon I'll give that try. I didn't actually realise that middleware could return and that would then be the return value of dispatch()!
Yep, this should work as long as any middleware in chain before it takes care to return next return value. Which well-behaved middleware should do.
@gaearon , thanks for the answer above. I'm still wrapping my head around writing custom middleware, and I had a quick question based on this comment:
Yep, this should work as long as any middleware in chain before it takes care to return next return value. Which well-behaved middleware should do.
You state that well-behaved middleware should return the value of next, yet the example you gave returns { ready, abort }. Since this isn't returning next, is this middleware still considered well-behaved?
My interpretation of this is that the xhr middleware you wrote above needs to be listed last in the middleware stack. Is that right, or am I misunderstanding something?
Update: I think I'm slowly getting a better understanding of this. Middleware doesn't seem to necessarily have to return next if it's handling the action itself. It seems next is used to forward along the action to the next middleware if the current middleware won't be handling it, or something. Still figuring it out tho' 馃挱
Keep in mind that you use 'next()' in your middleware simply to bypass the invocation of the current middleware again during this flow. If you had always used the same 'dispatch', you would end up in the same middleware again.
Most helpful comment
There currently is no cancellation support in
fetch()API although that may change in the future.If you change your middleware to use
XMLHttpRequestdirectly you can tweak it to return the{ abort }method.Then your components will have access to it as a part of
dispatch()return value: