Unexpected order of updates on data fetch
Opened this issue ยท 6 comments
Describe the bug
I believe this is unexpected behaviour, however maybe it's intentional and just a bit confusing.
Given a simple use of useFetch
:
function MyComponent() {
const { loading, data } = useFetch('/some/data/source');
console.log( { loading, data } );
if(loading) return <>Loading</>
if(!data) return <>Nothing found!</>
return <>Here's your {data}</>
}
The render cycle with a successful data response would result in the following log and three renders:
{ loading: true, data: null } // To be expected
{ loading: false, data: null } // Not loading anymore, but there's no data!
{ loading: false, data: { your: 'data' } } // The data is now populated
This middle render cycle with loading
false and data
(or error
) still set to the previous value, or null
for first render, makes it difficult to have a component trust the state of the fetch request. It also results in a flash of unwanted content, where your component renders incorrectly.
We caught this when we observed:
- Our Sentry logs were reporting that our users were seeing 404 pages
- In reality, they were just seeing a flash of a 404 page during first load. That render cycle was enough to trigger our error logging and was visible if you watched closely.
To Reproduce
I forked your demo and made the detail page simple:
- Go to https://k7r4mz.csb.app/details
- Open the console
- Refresh the page
- Observe the log lines - you will get a line with
No data found with the request!
You can view the sandbox here, go to pages/details/details.page.tsx
Expected behavior
There should not be an interim state where loading
is false but the result isn't set on data
or error
.
Additional context
If this is intended behaviour, a note on how to set up a page to avoid a flash of an unloaded state would be great. I want to avoid adding a block like if(result.loading || !result.data) return <Loading />
, as the absence of data on a successful request could be a valid response.
Hey @stabback, change is indeed intentional, however I will handle it in a patch to be less confusing and give the instant value with the lifecycle ๐๐ป
Oh wait, I missed the:
{ loading: false, data: null } // Not loading anymore, but there's no data!
This is not intentional and I will take a look to cover this bug ๐
Hey @stabback ! I released new version of HF - 6.0.0 wihich includes the fix for this issue. It is potentially breaking change, since it changes a bit the loading behavior - that's why it it's completely new major version. Let me know if I can help you with something ๐
Thanks for updating @prc5.
I was unable to have this solve my issue on my project, so I created a simpler code sandbox, but was surprised when it worked fine! Once I downgraded React to 16.14 though (the version this project is on), the double render came back.
It looks like this still happens on at least this version of React. If React 16 is no longer supported for this package, could I recommend updating your peerDeps accordingly? Otherwise, it'd be great to have this resolved.
Here's a barebones reproduction on React 16.14 (the one above was forked from your example, but that has a bunch of extra cruft in it): https://codesandbox.io/p/sandbox/hyperfetch-issue-94-reproduction-ld8dcm
As an aside, and definitely outside the scope of this issue - It'd be fantastic if the first render cycle reflected that the query was in flight. Right now, given the following code:
const { data, loading, error } = useFetch(...)
if(!data && !loading && !error) {
???
}
The component has no idea if either:
- The request completed successfully, and no data was returned
- The request hasn't started yet
It also causes an unnecessary immediate second render while @hyper-fetch dispatches the request and immediately sets this to loading.
I'm happy to make a new issue or start a discussion about this if you'd like.
Hey! It's the issue related to batching of the state updates(released with react 18) - I will cover this case too, as I want to keep it compatible with older versions ๐ซก