urql-graphql / urql

The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
https://urql.dev/goto/docs
MIT License
8.57k stars 445 forks source link

Query fetching status is temporarily flicking to false #3379

Closed GigEth closed 10 months ago

GigEth commented 10 months ago

Describe the bug

I am running a query using the useQuery hook and I am finding that the fetching state starts off true (as expected), temporarily flicks to false and then flicks back to true again until the query has actually finished. This causes a flicker in the UI as I rely on the fetching state to show a loading spinner/loading dialogue.

Request policy doesn't make any difference, it happens with 'network-only' so cannot be a cache issue.

I am struggling to reproduce this with any of your examples but I note with your examples the state doesn't flicker at all, just re-renders once when going from true to false as I would expect. Here is how mine looks, logging out the state of fetching:

Screenshot 2023-09-12 at 17 15 21

I am on hand to help as I noted somebody else reported something similar a while back.

Reproduction

https://stackblitz.com/edit/github-zagfjn?file=src%2FPokemonList.jsx

Urql version

urql v4.0.5

Validations

kitten commented 10 months ago

Sorry, I don't really have time to check and explain the entire flow and step through all of it, but to summarise what's going on, not all renders will actually lead to a React update and effects being run.

A hook is actually able to invalidate a render at any time. What this means is that the state changes while the component renders, i.e. while the component function is called. This triggers a de-opt like state change, which causes the component function to be called again, which is used to invalidate invalid states. None of the updates or effects from the prior run will be used. However, these calls will of course be reflected still if you log in the function body of a component.

If you instead schedule an effect, you won't see these intermediary states, even if the effect has no dependencies, i.e. even if it triggers on each update: https://stackblitz.com/edit/github-zagfjn-yyrsqj?file=src%2FPokemonList.jsx

Edit: to expand, There's more issues where I'm explaining this, but given the constraints that we have (including different modes and versions of React rendering) and given all edge cases, this is, afaik, one of the only ways to tick all the boxes, to get the blessing of a React team member in the past, and to not cause any inconsistent states to actually cause updates

GigEth commented 10 months ago

Hi @kitten, okay I can accept that might be a false flag but what certainly is not is my UI flashing because the loading state is temporarily flicking to false and then flicking back to true. I am still trying to replicate this with your examples.

kitten commented 10 months ago

100% true — if you have a reproduction of this in the examples, I'll have to look into that and see if there's a reproducible condition that causes this faulty state.

GigEth commented 10 months ago

Hi @kitten I have worked out the cause and I owe you an apology as I am not sure this is a bug but I want to confirm plus also ask for some advice on how I might resolve it.

We use a header that we pass to all our queries and mutations, of which the value is stored as a cookie in the browser. We retrieve the value using react-cookie

const [cookies] = useCookies([JWT])

and then set it using fetchOptions when we create the client

  const appClient = createClient({
    url: https://gateway.com,
    fetchOptions: () => {
      return {
        headers: { 'token': cookies[JWT] },
      }
    },
    exchanges: [UrqlModule.cacheExchange, authExchange, UrqlModule.fetchExchange]
  })

We do this in our very root/top-level component and then we pass the created client into the provider.

The issue seems to be that this is causing a re-render (it looks like the cookie hook is causing this when it gets the value of the cookie which I would expect) and this is causing the client to fetch again, so this is where the flicker comes from. My question is: is there a better way to do this or a way that will resolve this problem?

kitten commented 10 months ago

@GigEth I'm not quite sure what your token does or why it's derived from a hook, but I'd basically recommend for you to think about why it starts out as a hook and how to keep that state away from your Client. I'd try to basically not re-create the Client dependent on the token. Since you already have an authExchange that's a similar situation