How to urql, authentication & multiple users
Tags: javascript, react, urql, graphql
Published: 2020-03-04 16:43:14

## Introduction

In the [last blog-post](https://dev.to/jovidecroock/how-to-urql-basics-527j) we covered the basics on how to query and mutate our data; in real-world applications, there's more to it. In this post, we'll cover setting an authentication token and handling multiple users interacting with the same data.

You can follow along by using [this template.](https://codesandbox.io/s/empty-meadow-4lwu1)

The template above builds on the example we introduced in the previous blog post.

## Authentication

Authentication is one of the most common needs in an application. When users log in, we need to provide an authentication token that we can use in requests.

First, let's build our login flow and change the behavior of our app so that users can't complete todos unless they have an authentication token.

When we navigate to `Login.js`, we see that there's a basic setup built for us, we have a `<form>` with an `onSubmit`, and an `<input>` controlling a variable called `name`.

We'll use the `useMutation` hook, which we discussed in the previous post, to log in and get a token.

```js
import { useMutation } from 'urql';

export const Login = ({ setIsAuthenticated }) => {
  const [name, setName] = React.useState("");

  const [data, login] = useMutation(`
      mutation ($name: String!) {
          login (name: $name)
      }
  `);

  const handleSubmit = (e) => {
    e.preventDefault(); // no page reload due to submit
    login({ name }).then(({ data }) => {
      if (data.login) {
        setToken(data.login);
        setIsAuthenticated(true);
      }
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Login</h2>
      <input value={name} onChange={e => setName(e.currentTarget.value)} />
      <button disabled={data.fetching} type="sumbit">Log in!</button>
    </form>
  )
}
```

Once we have our token, the `setToken` method stores it in `localStorage`, and we notify the parent that we are authenticated with the `setIsAuthenticated` method.

After logging in we can see our todos, but we are no yet able to toggle the state of a todo. We still need to tell `urql` to send our authentication token to our server. The `urql` client has a property called `fetchOptions` that can be used to add data to our fetch request. This property can be set when we create the client. Let's go back to `App.js` and add the `fetchOptions` property so we can send the authentication token along with the `toggleTodo` request.

```js
const client = createClient({
  ...
  fetchOptions: () => {
    const token = getToken();
    return token ? { headers: { Authorization: `Bearer ${token}` } } : {};
  },
});
```

The `fetchOptions`  method can accept a function or an object. We will use a function so it will be executed every time we make a fetch request, and will always send an up-to-date authentication token to the server. 

## Consistent data

What if we want to build a shared todo app, and keep track of the last person to toggle each todo by means of an `updatedBy` field? How can we make sure our data gets updated correctly and keep our UI from getting outdated when multiple people are interacting with the same data?

A simple solution would be to add polling to our `useQuery` hook. Polling involves repeatedly dispatching the same query at a regular interval (specified by  `pollInterval`). With this solution, we need to be aware of caching. If our `requestPolicy` is `cache-first` or `cache-only` then we'll keep hitting the cache and we won't actually refetch the data. `cache-and-network` is an appropriate `requestPolicy` for a polling solution.

Let's look at how our query looks after adding a `pollInterval` — let's say we want to refetch our todos every second.

```js
const [data] = useQuery({
  query: `...`,
  requestPolicy: 'cache-and-network',
  pollInterval: 1000,
});
```

While refetching,  `data.stale` will be `true` since we are serving a cached result while a refetch is happening.

We can test this by opening a new browser window and toggling a todo. We'll see that after the polled request completes the data will be in sync again. We can increase the `pollInterval` to see this more clearly.

Polling is a straight-forward solution, but dispatching network requests every second, regardless of whether anything has changed, is inefficient. Polling can also be problematic in situations where data is changing rapidly since there's still a time-window between requests where data can get out of sync. Let's remove the `pollInterval` and look at another option.

GraphQL contains another root field, the two we know now are `query` and `mutation` but we also have `subscription`, which builds on `websockets`. Instead of polling for changes, we can subscribe to events, like toggling the state of a todo.

In the last post, we touched on the concept of exchanges. Now we're going to add one of these exchanges to make our client support `subscriptions`. `urql` exposes the `subscriptionExchange` for this purpose, this is a factory function that returns an exchange.

Let's start by adding a transport-layer for our `subscriptions`.

```sh
npm i --save subscriptions-transport-ws
# or 
yarn add subscriptions-transport-ws
```

Now we can add the `subscriptionExchange` to the exchanges of our client!

```js
import {
  cacheExchange,
  createClient,
  dedupExchange,
  fetchExchange,
  subscriptionExchange,
} from 'urql';
import { SubscriptionClient } from 'subscriptions-transport-ws';
    
const subscriptionClient = new SubscriptionClient(
  'wss://k1ths.sse.codesandbox.io/graphql',
  {},
);
    
const subscriptions = subscriptionExchange({
  forwardSubscription: operation => subscriptionClient.request(operation), 
});
    
const client = createClient({
  ...
  exchanges: [
    dedupExchange,
    cacheExchange,
    fetchExchange,
    subscriptions,
  ],
});
```

The ordering of exchanges is important: We want to first deduplicate our requests, then look into the cache, fetch it when it's not there, and run a subscription if it can't be fetched.

Now we are ready to alter the way we currently handle our todos data. Because we don't want to mutate the array of todos we get returned from `urql` we will introduce a mechanism based on `useState` and `useEffect` to save them in our own state.

This way we can have the `useSubscription` alter our state instead of keeping its own internal state.

```js
import { useQuery, useSubscription } from 'urql';

const Todos = () => {
  const [todos, setTodos] = React.useState([]);
  const [todosResult] = useQuery({ query: TodosQuery }));

  // We're making a mutable reference where we'll keep the value
  // for fetching from the previous render.
  const previousFetching = React.useRef(todosResult.fetching);

  useSubscription(
    {
      query: `
        subscription {
          updateTodo {
            id
            text
            complete
            updatedBy
          }
        }
      `
    },
    // This callback will be invoked every time the subscription
    // gets notified of an updated todo.
    (_, result) => {
      const todo = todos.find(({ id }) => id === result.updateTodo.id);
      if (todo) {
        const newTodos = [...todos];
        newTodos[todos.indexOf(todo)] = result.updateTodo;
        setTodos(newTodos);
      }
    }
  );

  React.useEffect(() => {
    // When we transition from fetching to not fetching and we have
    // data we'll set these todos as our current set.
    if (previousFetching.current && !todosResult.fetching && todosResult.data) {
      setTodos(todosResult.data.todos);
    }
    // set the fetching on the mutable ref
    previousFetching.current = todosResult.fetching;
  }, [todosResult]); // When our result changes trigger this.

  return todos.map(...)
}
```

We use a little trick to see if we transition from `fetching` in the previous render to having `data` in the next. When a subscription triggers, we find the old todo and update state to include its new value.

Now we have introduced a consistent UI that can be used by multiple users simultaneously!

*Note that we'll see a more elegant way of updating this todo when we reach the `normalized caching` post!*

## Conclusion

We've now learned how to handle authentication and keep our data consistent when there are multiple users interacting with it.

Next up we will be learning how to make our application more performant by using a normalized cache to avoid having to refetch on every mutation.