Svelte
NOTE: These API docs are for the v1 version of
@urql/svelte
rather than v2. In the meantime, please check out our API docs for our Svelte bindings instead!
Getting started
This "Getting Started" guide covers how to install and set up urql
and provide a Client
for
Svelte. The @urql/svelte
package, which provides bindings for Svelte, doesn't fundamentally
function differently from @urql/preact
or urql
and uses the same Core Package and
Client
.
Installation
Installing @urql/svelte
is quick and no other packages are immediately necessary.
yarn add @urql/svelte graphql# ornpm install --save @urql/svelte graphql
Most libraries related to GraphQL also need the graphql
package to be installed as a peer
dependency, so that they can adapt to your specific versioning requirements. That's why we'll need
to install graphql
alongside @urql/svelte
.
Both the @urql/svelte
and graphql
packages follow semantic versioning and
all @urql/svelte
packages will define a range of compatible versions of graphql
. Watch out
for breaking changes in the future however, in which case your package manager may warn you about
graphql
being out of the defined peer dependency range.
Note: if using Vite as your bundler, you might stumble upon the error Function called outside component initialization
, which will prevent the page from loading. To fix it, you must add @urql/svelte
to Vite's configuration property optimizeDeps.exclude
:
{ optimizeDeps: { exclude: ['@urql/svelte'], } // other properties}
Setting up the Client
The @urql/svelte
package exports a method called createClient
which we can use to create
the GraphQL client. This central Client
manages all of our GraphQL requests and results.
import { createClient } from '@urql/svelte';
const client = createClient({ url: 'http://localhost:3000/graphql',});
At the bare minimum we'll need to pass an API's url
when we create a Client
to get started.
Another common option is fetchOptions
. This option allows us to customize the options that will be
passed to fetch
when a request is sent to the given API url
. We may pass in an options object or
a function returning an options object.
In the following example we'll add a token to each fetch
request that our Client
sends to our
GraphQL API.
const client = createClient({ url: 'http://localhost:3000/graphql', fetchOptions: () => { const token = getToken(); return { headers: { authorization: token ? `Bearer ${token}` : '' }, }; },});
Providing the Client
To make use of the Client
in Svelte we will have to provide it via the
Context API. From a parent component to its child
components. This will share one Client
with the rest of our app, if we for instance provide the
Client
<script> import { createClient, setClient } from '@urql/svelte';
const client = createClient({ url: 'http://localhost:3000/graphql', });
setClient(client);</script>
The setClient
method internally calls Svelte's setContext
function. The @urql/svelte
package also exposes a getClient
function that uses getContext
to retrieve the Client
in
child components. This is used throughout @urql/svelte
's API.
We can also use a convenience function, initClient
. This function combines the createClient
and
setClient
calls into one.
<script> import { initClient } from '@urql/svelte';
initClient({ url: 'http://localhost:3000/graphql', });</script>
Queries
We'll implement queries using the operationStore
and the query
function from @urql/svelte
.
The operationStore
function creates a Svelte Writable store.
You can use it to initialise a data container in urql
. This store holds on to our query inputs,
like the GraphQL query and variables, which we can change to launch new queries. It also exposes
the query's eventual result, which we can then observe.
Run a first query
For the following examples, we'll imagine that we're querying data from a GraphQL API that contains todo items. Let's dive right into it!
<script> import { operationStore, query } from '@urql/svelte';
const todos = operationStore(` query { todos { id title } } `);
query(todos);</script>
{#if $todos.fetching}<p>Loading...</p>{:else if $todos.error}<p>Oh no... {$todos.error.message}</p>{:else}<ul> {#each $todos.data.todos as todo} <li>{todo.title}</li> {/each}</ul>{/if}
Here we have implemented our first GraphQL query to fetch todos. We're first creating an
operationStore
which holds on to our query
and are then passing the store to the query
function, which starts the GraphQL query.
The todos
store can now be used like any other Svelte store using a
reactive auto-subscription in Svelte. This means
that we prefix $todos
with a dollar symbol, which automatically subscribes us to its changes.
The query
function accepts our store and starts using the Client
to execute our query. It may
only be called once for a store and lives alongside the component's lifecycle. It will automatically
read changes on the operationStore
and will update our query and results accordingly.
Variables
Typically we'll also need to pass variables to our queries, for instance, if we are dealing with
pagination. For this purpose the operationStore
also accepts a variables
argument, which we can
use to supply variables to our query.
<script> import { operationStore, query } from '@urql/svelte';
const todos = operationStore(` query ($from: Int!, $limit: Int!) { todos(from: $from, limit: $limit) { id title } }`, { from, limit } );
query(todos);</script>
...
As when we're sending GraphQL queries manually using fetch
, the variables will be attached to the
POST
request body that is sent to our GraphQL API.
The operationStore
also supports being actively changed. This will hook into Svelte's reactivity
model as well and cause the query
utility to start a new operation.
<script> import { operationStore, query } from '@urql/svelte';
const todos = operationStore(` query ($from: Int!, $limit: Int!) { todos(from: $from, limit: $limit) { id title } }`, { from, limit } );
query(todos);
function nextPage() { // it's important that we assign a new reference to variables. $todos.variables = { ...$todos.variables, from: $todos.variables.from + $todos.variables.limit }; // OR $todos.variables.from += $todos.variables.limit; $todos.reexecute(); }</script>
<button on:click={nextPage}>Next page<button></button></button>
The operationStore
provides getters as well, so it's also possible for us to pass todos
around and
update todos.variables
or todos.query
directly. Both, updating todos.variables
and
$todos.variables
in a component for instance, will cause query
to pick up the update and execute
our changes.
Pausing Queries
In some cases we may want our queries to not execute until a pre-condition has been met. Since the
query
operation exists for the entire component lifecycle however, it can't just be stopped and
started at will. Instead, the query
's third argument, the context
, may have an added pause
option that can be set to true
to temporarily freeze all changes and stop requests.
For instance, we may start out with a paused store and then unpause it once a callback is invoked:
<script> import { operationStore, query } from '@urql/svelte';
const todo = operationStore( ` query { todo { id title } }`, undefined, { pause: true } );
query(todo);
function unpause() { $todo.context.pause = false; }</script>
<button on:click="{unpause}">Unpause</button>
Request Policies
The operationStore
also accepts another argument apart from query
and variables
. Optionally
you may pass a third argument, the context
object. The arguably
most interesting option the context
may contain is requestPolicy
.
The requestPolicy
option determines how results are retrieved from our Client
's cache. By
default, this is set to cache-first
, which means that we prefer to get results from our cache, but
are falling back to sending an API request.
Request policies aren't specific to urql
's Svelte bindings, but are a common feature in its core.
You can learn more about how the cache behaves given the four different policies on the "Document
Caching" page.
<script> import { operationStore, query } from '@urql/svelte';
const todos = operationStore(` query ($from: Int!, $limit: Int!) { todos(from: $from, limit: $limit) { id title } }`, { from, limit }, { requestPolicy: 'cache-and-network' } );
query(todos);</script>
...
As we can see, the requestPolicy
is easily changed by passing it directly as a "context option"
when creating an operationStore
. We can then read our context
option back from todos.context
,
just as we can check todos.query
and todos.variables
.
Internally, the requestPolicy
is just one of several "context options". The context
provides metadata apart from the usual query
and variables
we may pass. This means that
we may also change the Client
's default requestPolicy
by passing it there.
import { createClient } from '@urql/svelte';
const client = createClient({ url: 'http://localhost:3000/graphql', // every operation will by default use cache-and-network rather // than cache-first now: requestPolicy: 'cache-and-network',});
Context Options
As mentioned, the requestPolicy
option that we're passing to the operationStore
is a part of
urql
's context options. In fact, there are several more built-in context options, and the
requestPolicy
option is one of them. Another option we've already seen is the url
option, which
determines our API's URL.
<script> import { operationStore, query } from '@urql/svelte';
const todos = operationStore(` query ($from: Int!, $limit: Int!) { todos(from: $from, limit: $limit) { id title } }`, { from, limit }, { requestPolicy: 'cache-and-network' url: 'http://localhost:3000/graphql?debug=true', } );
query(todos);</script>
...
As we can see, the context
argument for operationStore
accepts any known context
option and
can be used to alter them per query rather than globally. The Client
accepts a subset of context
options, while the operationStore
argument does the same for a single query. They're then merged
for your operation and form a full Context
object for each operation, which means that any given
query is able to override them as needed.
You can find a list of all Context
options in the API docs.
Reexecuting Queries
The default caching approach in @urql/svelte
typically takes care of updating queries on the fly
quite well and does so automatically. Sometimes it may be necessary though to refetch data and to
execute a query with a different context
. Triggering a query programmatically may be useful in a
couple of cases. It can for instance be used to refresh data.
We can trigger a new query update by changing out the context
of our operationStore
. While we
can simply assign a new context value using $todos.context = {}
we can also use the store's
reexecute
method as syntactic sugar for this:
<script> import { operationStore, query } from '@urql/svelte';
const todos = operationStore(` query ($from: Int!, $limit: Int!) { todos(from: $from, limit: $limit) { id title } }`, { from, limit }, { requestPolicy: 'cache-first' } );
query(todos);
function refresh() { todos.reexecute({ requestPolicy: 'network-only' }); }</script>
Calling refresh
in the above example will execute the query again forcefully, and will skip the
cache, since we're passing requestPolicy: 'network-only'
.
Furthermore the reexecute
function can also be used to programmatically start a query even
when pause
is set to true
, which would usually stop all automatic queries. This can be used to
perform one-off actions, or to set up polling.
Reading on
There are some more tricks we can use with operationStore
.
Read more about its API in the API docs for it.
Mutations
The mutation
function isn't dissimilar from the query
function but is triggered manually and
can accept a GraphQLRequest
object, while also supporting our
trusty operationStore
.
Sending a mutation
Let's again pick up an example with an imaginary GraphQL API for todo items, and dive into an example! We'll set up a mutation that updates a todo item's title.
<script> import { mutation } from '@urql/svelte';
export let id;
const mutateTodo = mutation({ query: ` mutation ($id: ID!, $title: String!) { updateTodo (id: $id, title: $title) { id title } } `, });
function updateTodo(newTitle) { mutateTodo({ id, title: newTitle }); }</script>
This small call to mutation
accepts a query
property (besides the variables
property) and
returns an execute function. We've wrapped it in an updateTodo
function to illustrate its usage.
Unlike the query
function, the mutation
function doesn't start our mutation automatically.
Instead, mutations are started programmatically by calling the function they return. This function
also returns a promise so that we can use the mutation's result.
Using the mutation result
When calling mutateTodo
in our previous example, we start the mutation. To use the mutation's
result we actually have two options instead of one.
The first option is to use the promise that the mutation
's execute function returns. This promise
will resolve to an operationStore
, which is what we're used to from sending queries. Using this
store we can then read the mutation's data
or error
.
<script> import { mutation } from '@urql/svelte';
export let id;
const mutateTodo = mutation({ query: ` mutation ($id: ID!, $title: String!) { updateTodo (id: $id, title: $title) { id title } } `, });
function updateTodo(newTitle) { mutateTodo({ id, title: newTitle }).then(result => { // The result is an operationStore again, which will already carry the mutation's result console.log(result.data, result.error); }); }</script>
Alternatively, we can pass mutation
an operationStore
directly. This allows us to use a
mutation's result in our component's UI more easily, without storing it ourselves.
<script> import { operationStore, mutation } from '@urql/svelte';
export let id;
const updateTodoStore = operationStore(` mutation ($id: ID!, $title: String!) { updateTodo (id: $id, title: $title) { id title } } `);
const updateTodoMutation = mutation(updateTodoStore);
function updateTodo(newTitle) { updateTodoMutation({ id, title: newTitle }); }</script>
{#if $updateTodoStore.data} Todo was updated! {/if}
Handling mutation errors
It's worth noting that the promise we receive when calling the execute function will never
reject. Instead it will always return a promise that resolves to an operationStore
, even if the
mutation has failed.
If you're checking for errors, you should use operationStore.error
instead, which will be set
to a CombinedError
when any kind of errors occurred while executing your mutation.
Read more about errors on our "Errors" page.
mutateTodo({ id, title: newTitle }).then(result => { if (result.error) { console.error('Oh no!', result.error); }});
Reading on
This concludes the introduction for using urql
with Svelte. The rest of the documentation
is mostly framework-agnostic and will apply to either urql
in general, or the @urql/core
package,
which is the same between all framework bindings. Hence, next we may want to learn more about one of
the following to learn more about the internals: