Context
Thanks for the very useful hook. We were going to build something similar using AbortController
and found that you published something that suits our needs.
I have one request that I wanted to bounce off you to optimize the number of times the component lifecycle runs. React currently only batches state updates together inside event handlers (i.e. side-effects). When there are setState
calls outside of event handlers (e.g. after an async fetch), the updates will not be batched. This results in multiple unnecessary renders.
In the case of use-abortable-fetch
, the result is four renders, which could be reduced to two renders if the state updates were batched.
Example
Consider the following case:
const { data, loading, error } = useAbortableFetch('/api/some-endpoint');
console.log('data', data);
console.log('loading', loading);
console.log('error', error);
console.log('--------------------------');
This will result in the below output:
data null
loading false
error null
--------------------------
data null
loading true
error null
--------------------------
data {…}
loading true
error null
--------------------------
data {…}
loading false
error null
--------------------------
As you can see, a single fetch is represented by four unique states, that results in four renders:
- the initial non-loading null state
- the loading state
- the loading + data state
- the non-loading + data state
Potential solutions
Either of the below options would require some bit of refactoring, but would result in the optimal number of renders, and the ideal output for the above example:
data null
loading true
error null
--------------------------
data {…} <--- or data will be null if error is set
loading false
error null
--------------------------
Option 1 [recommended] - refactor to minimize the number of calls to setState
Instead of updating the state 3x, only update it 1x (when the fetch completes / fails).
If you return the default state with loading=true
(instead of setting it in useEffect
), then you can combine renders 1 & 2. Then, if you update the data={}
& loading=false
states at once, then you can combine renders 3 and 4.
Renders 3
and 4
are already combined in the error
case
Option 2 - unstable_batchedUpdates
There exists a temporary API to force batching. If you wrap the setState
calls in ReactDOM.unstable_batchedUpdates
, then multiple calls will be batched.
This option still requires some refactoring, because all of the setState
calls would need to be in the same batchedUpdates(() => {})
callback for it to function correctly.
The react team expects to remove this API in the future and instead batch everything by default.
More info
@see facebook/react#14259