kesne / svelte-relay

Easily use GraphQL in Svelte, powered by the production-ready Relay runtime.
https://kesne.github.io/svelte-relay/
108 stars 5 forks source link

Come up with refetching patterns for using previous results #11

Open kesne opened 4 years ago

kesne commented 4 years ago

Currently, the only patterns we have for refetching data don’t allow you to re-use the existing data when fetching new data. This is important for UIs such as pagination, where we want to display the previous page when fetching a new page.

The relay hooks leverage concurrent mode and suspense to accomplish this, which we can’t do in Svelte. Apollo does this by setting the loading state back to true while still providing the old data, which we also can’t really do because of the await block.

kesne commented 4 years ago

My current idea is to borrow the idea of a "lazy query" from Apollo. We could mix this with reactive expressions

<script>
let page = 0;

const lazyQuery = getLazyQuery(graphql``);
$: query = lazyQuery({ page });

function nextPage() {
  page += 1;
}
</script>

{#await $query then data}
  <ul>
    {#each data.items as item}
      <li>{item.name}</li>
    {/each}
  </ul>
  <button disabled={$lazyQuery.isInFlight} on:click={nextPage}>Load next page</button>
{/await}

The type definitions for this would be roughly:

interface LazyQuery<T> extends Readable<{ isInFlight: boolean }> {
  (options: QueryOptions): Readable<Promise<T>>;
}

A slight variation of this API would be to array from getLazyQuery, rather than requiring users to use the return value from calling lazyQuery.

kesne commented 4 years ago

An alternative could be to create a "refetch container" which could wrap the getQuery() function, and would ensure that whenever we've resolved the promise once, we don't ever flip back into the loading state.

This approach could also work for our implementation of refetchable fragments.

let page = 0;

const refetch = getRefetchContainer();
$: query = refetch(getQuery(graphql``, { page }));

The refetch container would only be created once per component, and whenever the underlying readable store is changed, it would know to preserve the previous values until the promise in the new store is resolved.

The refetch container itself would implement readable to determine if there is a refetch currently happening or not. The type definitions would be roughly:

interface RefetchContainer<T> extends Readable<{ isInFlight: boolean }> {
  (query: T): T
}
kesne commented 4 years ago

Another option would be to create a module-level cache, where the GraphQL query is our cache key. The cache would let us avoid moving back to the loading state when we replace stores. Because we also would need to expose refetching state, we would need to return a tuple, with the second parameter being the refetching state.

let page = 1;
$: [query, state] = getRefetchQuery(graphql``, { page });

If we want, we could also just make this the API for the normal getQuery API, and add something like a render policy which would allow the user to define if they want to re-use the existing store data when re-fetching, or if it should not.

I verified that this should be technically possible in the repl: https://svelte.dev/repl/dd31d26517344985a9f3a4d4f19ead3a?version=3.22.3

The cache would be reference counted so that we could clean it up.

kesne commented 4 years ago

Another approach would be to use a tuple, and move the variable usage into the actual auto-subscription of the store. This would remove the need for any reactive statements, as the rendering of the component itself would make sure that the updated variables.

We'd need to document how to avoid the re-fetching of data if the variables do change.

<script>
let page = 0;

const [query, state] = getQuery({ page });

function nextPage() {
  page += 1;
}
</script>

{#await $query({ page }) then data}
  <ul>
    {#each data.items as item}
      <li>{item.name}</li>
    {/each}
  </ul>
  <button disabled={$state.isInFlight} on:click={nextPage}>Load next page</button>
{/await}

https://svelte.dev/repl/ef1fd97bc131486c8b3d97deda318242?version=3.22.3