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.65k stars 452 forks source link

RFC: `svelte` - avoid `pause: true` delete already populated `data` #3246

Closed frederikhors closed 9 months ago

frederikhors commented 1 year ago

The code I'm using for the common pattern list > details in my Svelte app is like this:

<script lang="ts">
  import { queryStore, gql, getContextClient } from '@urql/svelte';
  import { TODOS_QUERY } from "./queries";

  export let listVisible = true;

  $: todos = queryStore({
    client: getContextClient(),
    query: TODOS_QUERY,
    pause: !listVisible,
  });
</script>

As you can imagine listVisible is used to determine if this list should be visible on the page.

If I click on a todo the list disappears (I apply an hidden css class on it) and the todo shows up.

I'm using the pause option because I would like to "freeze" the todos query when it is not visible.

And I would like to "unfreeze" it when it becomes visible again.

The "unfreeze" part should also "re-execute" it.

What is happening now

This worked good until "@urql/svelte": "2.x".

When listVisible === false the todos.data becomes undefined. There is no data in todos anymore.

But it reloads the query (with an API call) when listVisible === true again.

This is really inconvenient if I want to hide the list rather than recreate it every time I enter an item and go back (especially when every single component of the list is very heavy to render; this technique is used by Gmail just to name one).

What I'm using right now

<script lang="ts">
  import { queryStore, gql, getContextClient } from '@urql/svelte';
  import { TODOS_QUERY } from "./queries";

  export let listVisible = true;

  $: todos = queryStore({
    client: getContextClient(),
    query: TODOS_QUERY,
    pause: true,
  });

  $: if (listVisible) {
    listStore.resume();
  } else {
    listStore.pause();
  }
</script>

Which as you can see is very ugly and not idiomatic.

What would be amazing

I would like to rely on pause here.

If a query has already populated its data field why make it disappear? Why reload it?

kitten commented 1 year ago

afaik, all we can do here is allow pause to be a readable boolean store, however, this isn't really like Vue, where this, from a library standpoint, is idiomatic afaict.

Just to clarify, the code stated doesn't delete the data. i.e. this:

  $: todos = queryStore({
    client: getContextClient(),
    query: TODOS_QUERY,
    pause: !listVisible,
  });

In fact, what's happening here is that queryStore gets explicitly re-created, as per this being a reactive statement.

I'll have to research whether there's a way to do this without the implementation becoming too convoluted, but I'm not 100% sure as of now

JoviDeCroock commented 10 months ago

After looking into this and giving it some thought, it might be better to perform a synchronous cache-read and unsubscribe immediately when we see a query is paused but that could be undesired behaviour as well when it starts out as paused 😅 it feels like the re-creation aspect of Svelte really fights you with retaining old data. I think for now to achieve this behaviour one might want to change the requestPolicy to cache-only

JoviDeCroock commented 9 months ago

Closing this as it seems like the store approach does not lend itself to this easily 😅 the above comment illustrates how stores get deleted, to support this we'd basically have to hack around that and re-query the previous operation and see if we get a synchronous result to keep it around.