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.61k stars 449 forks source link

Add `reexecute()` to `@urql/svelte` (again!) #3221

Closed frederikhors closed 7 months ago

frederikhors commented 1 year ago

Summary

Upgrading from @urql/svelte 1 to 2 I missed reexecute() which is very useful sometimes when not using the reactive mode ($:) that Svelte offers.

Proposed Solution

Can we re-add it, @jonathanstanley?

Notes

It is been asked from other people too:

(BTW: did you find an alternative way?)

rouzwelt commented 1 year ago

Summary

Upgrading from @urql/svelte 1 to 2 I missed reexecute() which is very useful sometimes when not using the reactive mode ($:) that Svelte offers.

Proposed Solution

Can we re-add it, @jonathanstanley?

Notes

It is been asked from other people too:

(BTW: did you find an alternative way?)

I figured out a workaround using svelte reactive block

frederikhors commented 1 year ago

I figured out a workaround using svelte reactive block

Thanks, can you show us?

rouzwelt commented 1 year ago

I figured out a workaround using svelte reactive block

Thanks, can you show us?

sure, take a look here: https://github.com/rainprotocol/rain-toolkit-gui/blob/304e6b49d4d365f0a358f9bad6c46a485d927213/src/routes/sale/SaleChart.svelte#L65

the refresh() method is responsible for performing a similar thing to reexecute(), which is bound to a spinning arrow button on html.

a few lines higher you can see the query is also bound to a svelte reactive variable. Hope it helps, let me know if you have any questions

kitten commented 1 year ago

The docs state another method: https://formidable.com/open-source/urql/docs/basics/svelte/#reexecuting-queries

And there's also options to reassign the original store, so I'm not sure there's anything here that warrants implementing unless:

frederikhors commented 1 year ago

Thanks for the answer, @kitten.

In Svelte is very frequent to have code like:

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

  export let condition;
  export let pagination;
  export let orderBy;
  export let canBeVisible;

  const client = getContextClient();
  const query = gql`
    query {
      todos {
        id
        title
      }
    }
  `

  $: todos = queryStore({
    client,
    query,
    variables: {
      input: {
        pagination,
        condition: {
          ...condition,
          player: { name: true },
          show_team: { name: true },
        },
        orderBy,
      }
    },
    pause: !canBeVisible,
  });
</script>

First issue

The code you propose in docs:

function refresh() {
  queryStore({
    client,
    query,
    requestPolicy: 'network-only'
  });
}

forces us to repeat many of the instructions already contained in $: todos... (e.g.: pause, variables and we should make them $: reactive too).

Second issue

Sometimes we need to be really fast in coding and we would like to avoid writing a custom refresh() function if we can (even in templates) call directly:

<button on:click={() => todos.reexecute()}>Refresh results</button>

Proposal

It would be amazing to have a reexecute() method to use, such as:

$: todos = queryStore({
  client,
  query,
  //...
});

// So I can use anywhere:
todos.reexecute()
patsissons commented 7 months ago

ran into the mimssing reexecute() tonight where i wanted to poll on an interval, here is a relatively svelte-y reactive workaround. hope this is helpful for others.

// $lib/queries.ts

import {
  type AnyVariables,
  getContextClient,
  gql,
  type QueryArgs,
  queryStore,
  type OperationResultState,
} from '@urql/svelte'
import { derived, readable, writable } from 'svelte/store'

function withPolling<Data = unknown, Variables extends AnyVariables = AnyVariables>(
  queryArgs: QueryArgs<Data, Variables>,
) {
  const initial = queryStore(queryArgs)
  const result = writable<OperationResultState<Data, Variables>>()
  const query = derived([initial, result], ([initial, next]) => {
    return next || initial
  })

  return {
    ...query,
    refresh,
    poll,
  }

  function refresh() {
    return new Promise<Data>((resolve, reject) => {
      queryStore<Data, Variables>({ ...queryArgs, requestPolicy: 'network-only' }).subscribe(
        ($data) => {
          result.set($data)

          if ($data.error) {
            reject($data.error)
          } else if ($data.data) {
            resolve($data.data)
          }
        },
      )
    })
  }

  function poll(intervalMs = 5_000) {
    const lastUpdated = readable(new Date(), (set) => {
      const timeout = setInterval(interval, intervalMs)

      return () => {
        clearInterval(timeout)
      }

      function interval() {
        refresh().catch(console.error)
        set(new Date())
      }
    })

    const polling = derived([query, lastUpdated], ([query, lastUpdated]) => ({
      ...query,
      lastUpdated,
    }))

    return {
      ...polling,
      refresh,
    }
  }
}

export function queryTodos() {
  return withPolling({
    client: getContextClient(),
    query: gql`
      query {
        todos {
          id
          title
        }
      }
    `,
  })
}

<script lang="ts">
  import { queryTodos } from '$lib/queries'

  const query = queryTodos().poll()

  $: todos = $query.data?.todos

  function refresh() {
    // this will also update todos above
    query.refresh().then(console.log).catch(console.error)
  }
</script>

<button on:click={refresh}>Refresh</button>
<pre>{JSON.stringify(todos)}</pre>