BetterTyped/hyper-fetch

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:

  1. Our Sentry logs were reporting that our users were seeing 404 pages
  2. 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:

  1. Go to https://k7r4mz.csb.app/details
  2. Open the console
  3. Refresh the page
  4. 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.

prc5 commented

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 ๐Ÿ‘๐Ÿป

prc5 commented

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 ๐Ÿ›

prc5 commented

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

image
image

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:

  1. The request completed successfully, and no data was returned
  2. 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.

prc5 commented

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 ๐Ÿซก