Getting Started

Installation

Installing urql is as quick as you'd expect. Firstly, install it with your package manager of choice:

yarn add urql
# or
npm install --save urql

Then, if you haven't already, make sure that all peer dependencies are installed as well:

yarn add react react-dom graphql
# or
npm install --save react react-dom graphql

Note: Most libraries related to GraphQL specify graphql as their peer dependency so that they can adapt to your specific versioning requirements. The library is updated frequently and remains very backwards compatible, but make sure it will work with other GraphQL tooling you might have installed.

Creating a client

Like similar libraries that manage state and data, you will need to wrap your app with urql's <Provider>. This <Provider> holds the Client that is used to manage data, requests, the cache, and other things. It's the "heart" of urql and holds all of its core logic.

This example creates a Client, passes it a GraphQL API's URL, and provides it using the <Provider>.

import { Provider, createClient } from 'urql';

const client = createClient({
  url: 'http://localhost:4000/graphql',
});

const YourApp = () => (
  <Provider value={client}>
    {/* ... */}
  </Provider>;
);

A tutorial on how to set up a client and Provider is available as screencast on egghead.

Every component and query underneath the <Provider> in the tree now has access to the client and will call the client when it needs to execute GraphQL requests.

The client accepts more options these can be found in the API

Writing queries

To illustrate how this works, the next example will use urql's useQuery to fetch some GraphQL data.

import React from 'react';
import { useQuery } from 'urql';

const getTodos = `
  query GetTodos($limit: Int!) {
    todos(limit: $limit) {
      id
      text
      isDone
    }
  }
`;

const TodoList = ({ limit = 10 }) => {
  const [result] = useQuery({
    query: getTodos,
    variables: { limit },
  });

  if (result.fetching) return 'Loading...';
  if (result.error) return 'Oh no!';

  return (
    <ul>
      {result.data.todos.map(({ id, text }) => (
        <li key={id}>{text}</li>
      ))}
    </ul>
  );
};

When this hook is executed it will send the query and variables to your GraphQL API. Here we're using fetching to see whether the request is still being sent and is loading, error to see whether any errors have come back, data to get the result, and finally extensions to get any arbitrary extensions data the server may have optionally returned.

Whenever the query or variables props change, the useQuery hook will send a new request and go back into the fetching state.

The shape of the result include data and error which is rather similar to the response a GraphQL API sends back by default. However, the error is not the plural errors. urql wraps any network error or GraphQL errors in a CombinedError which is more convenient to handle and observe.

Read more about the result's API in the Architecture's Results section.

A tutorial on the useQuery hook is also available as a screencast on egghead.

Using graphql-tag

You're not limited to just passing in strings as queries. You can also pass in a fully parsed AST in the form of DocumentNode instead. For this purpose you can use graphql-tag.

This can be extremely helpful, since it enables syntax highlighting in some editors. It also can be used to preparse the GraphQL query using babel-plugin-graphql-tag or the included Webpack loader.

You only have to make a small adjustment. Install graphql-tag and you can immediately write tagged template literals instead:

import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from 'urql';

const getTodos = gql`
  query GetTodos($limit: Int!) {
    todos(limit: $limit) {
      id
      text
      isDone
    }
  }
`;

Keep in mind that it makes sense to give your queries unique names. In this case we've chosen GetTodos, since we're simply listing out some Todos.

Find out more about graphql-tag on their repository.

Writing mutations

There always comes a point when an app will also need to send mutations to the GraphQL API. A mutation's response is very similar to a query's response, but often they're used in multiple use cases.

Sometimes you care about the response, sometimes you don't, sometimes it might make more sense to imperatively use the mutations' result.

To support all these use cases urql's useMutation hook is quite flexible. The return value has object that contains the executeMutation method that accepts variables as its first argument. When called it will return a Promise with the mutations result.

However, the useMutation hook will expose the result as well, like the useQuery hook exposes it, with a fetching, data, and an error property.

Here's an example of an imperative use case where we create a todo.

import React, { Component } from 'react';
import { useMutation } from 'urql';

const addTodo = `
  mutation AddTodo($text: String!) {
    addTodo(text: $text) {
      id
      text
    }
  }
`;

const TodoForm = () => {
  const [addTodoResult, addTodo] = useMutation(addTodo);
  if (addTodoResult.error) {
    return 'Oh no!';
  }

  const add = () => {
    addTodo({ text: 'learn urql' })
      .then(result => {
        // You can do something here or use the result object on the useMutation
      })
      .catch(error => {
        // You can do something here if it throws
      })
  }

  return (
    <button onClick={add}>
      Add something!
    </button>;
  );
}

When using the useMutation hook we have the choice to use .catch or .then to manually look at our result or we can use the second argument to look at what the API has returned for said mutation.

This executeQuery method accepts a second argument which is the OperationContext

A tutorial on the useMutation hook is also available as a screencast on egghead.

Refetching data

urql will by default come with a simple "document" cache. Each query with variables that is requested from a GraphQL API, the result will be cached completely. When the same query and variables are requested again, urql's default cache will then return the cached result. This result is also invalidated when a mutation with similar __typenames was sent.

You can find out more about the default caching behavior in the Basics' cacheExchange section.

Using urql's default behavior this means we sometimes need a way to refetch data from the GraphQL API and skip the cache, if we need fresh data.

The easiest way to always display up-to-date data is to set the requestPolicy to 'cache-and-network'. Using this policy urql will first return a cached result if it has one, and subsequently it will send a new request to the API to get the up-to-date result. When urql is refreshing data in the background due to cache-and-network your result will also carry stale: true on its payload.

A requestPolicy can be passed as a prop:

<Query query={q} requestPolicy="cache-and-network" />;

/* or with hooks: */

useQuery({ query: q, requestPolicy: 'cache-and-network' });

Including 'cache-and-network' there are four request policies in total:

  • cache-first: The default policy. It doesn't send a request to the API when a result can be retrieved from the cache.
  • cache-only: It never sends a request and always uses the cached or an empty result.
  • network-only: This skips the cache entirely and always sends a request.
  • cache-and-network: As stated above, this returns the cached result and then also sends a request to the API.

You can find out more about how the default cache behaves when it receives these request policies in the Basics' cacheExchange section.

Next, we can take a look at how to use 'network-only' to force a refetch imperatively. In our previous example this would come in handy to refresh the list of todos.

import React from 'react';
import { useQuery } from 'urql';

const getTodos = `
  query GetTodos {
    todos(limit: 10) {
      id
      text
      isDone
    }
  }
`;

const TodoList = ({ limit = 10 }) => {
  const [{ fetching, data, error, extensions }, executeQuery] = useQuery({
    query: getTodos,
    variables: { limit },
  });

  if (fetching) return 'Loading...';
  if (error) return 'Oh no!';

  return (
    <div>
      <ul>
        {data.todos.map(({ id, text }) => (
          <li key={id}>{text}</li>
        ))}
      </ul>
      <button onClick={() => executeQuery({ requestPolicy: 'network-only' })}>
        Refresh
      </button>
    </div>
  );
};

As can be seen, the useQuery hook also expose an executeQuery method, which isn't unlike the useMutation hook with its executeMutation method.

We can call this method to rerun the query and pass it a requestPolicy different. In this case we'll pass 'network-only' which will skip the cache and make sure we actually refresh our todo list.

This executeQuery method accepts everything that's available on an OperationContext

Pausing queries

Let's say our query needs a userId to correctly execute our query, we don't want to dispatch the query to receive an error in this scenario we can avoid.

import React from 'react';
import { useQuery } from 'urql';

const getUser = `
  query getUser (id: $id) {
    user(id: $id) {
      id
      name
    }
  }
`;

const Profile = ({ userId }) => {
  const [{ data, fetching, error }] = useQuery({
    query: getUser,
    variables: { id: userid },
    pause: !userId,
  });

  if (!userId) return 'Please select a "userId".';
  if (fetching) return 'Loading...';
  if (error) return 'Oh no!';

  return <p>Welcome {data.user.me}</p>;
};

When the user now selects a userId pause will evaluate to false and the query will be executed with the new variable.

Polling

Your query can be passed an argument named pollInterval, this will ensure that your query is reexecuted every x ms. You'll have to make sure that your requestPolicy for these queries is cache-and-network or network-only else the data will never be refetched.

More examples

More examples on how to use urql can be found in the repository's examples/ folder.