GraphQL and React (Native)

Kadi Kraman

@kadikraman

The urql edition

Kadi Kraman

Engineering Manager

Formidable

Topics

Exchanges

Caching

Authentication

What is urql all about?

Dev Tools

Offline Support

Community

Apollo Client

GraphQL Clients for React (Native)

urql

Relay

What is urql all about?

 our place in the ecosystem was to challenge existing solutions and to be flexible enough to adopt new ideas

- Phil Plückthun, urql core maintainer

  • alternative to Apollo Client
  • first launched ~2 years ago
  • fast, reliable, extensible, comprehensive

My experience with GraphQL + RN

First RN + gql project

August 2020

(using Apollo Client)

(using urql)

October 2017

Latest RN + gql project

.

.

.

.

various RN projects with and without GraphQL

.  .  .  .  .  .  .

.  .  .  .  .  .  .

.  .  .  .  .  .  .  .  .  .  .

Getting started

# npm
npm i --save urql graphql
# or yarn
yarn add urql graphql
import { createClient, Provider } from 'urql';

const client = createClient({
  url: 'https://0ufyz.sse.codesandbox.io'
});

const App = () => (
  <Provider value={client}>
    <Todos />
  </Provider>
);

Install

Create the client

npm install @apollo/client graphql
import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
} from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://48p1r2roz4.sse.codesandbox.io',
  cache: new InMemoryCache()
});

function App() {
  return (
    <ApolloProvider client={client}>
      <div>
        <h2>My first Apollo app 🚀</h2>
      </div>
    </ApolloProvider>
  );
}

Install

Create the client

urql

Apollo

import { useQuery } from 'urql';

const Todos = () => {
  const [res] = useQuery({
    query: `
      query { todos { id text } }
    `,
  });
  
  if (res.fetching) return <p>Loading...</p>;
  
  if (res.error) return <p>Errored!</p>;
  
  return (
    <ul>
      {res.data.todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
};

Query Data

import { useQuery, gql } from '@apollo/client';

const EXCHANGE_RATES = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      currency
      rate
    }
  }
`;

function ExchangeRates() {
  const { loading, error, data } = useQuery(EXCHANGE_RATES);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  return data.rates.map(({ currency, rate }) => (
    <div key={currency}>
      <p>
        {currency}: {rate}
      </p>
    </div>
  ));
}

urql

Apollo

Exchanges

Exchanges are a series of operations that transform the input (operations) stream to the output stream

Similar to Apollo link

it [urql] is so extensible that even the cache is an exchange

- Jovi De Croock, urql core maintainer

icons from flaticon.com

useQuery

data

How do Exchanges work?

fetchExchange - sends an operation to the API using fetch and adds results to the output stream

fetchExchange

useQuery

data

dedupExchange

dedupExchange - remove duplicate from pending operations

fetchExchange

useQuery

data

cacheExchange - the default caching logic with "Document Caching"

dedupExchange

fetchExchange

useQuery

data

cacheExchange

cacheExchange

dedupExchange

useQuery

data

fetchExchange

your own exchange with custom enhancements

cacheExchange

dedupExchange

useQuery

data

fetchExchange

Creating custom exchanges

import { createClient, dedupExchange, fetchExchange } from 'urql';

const noopExchange = ({ client, forward }) => {
  return operations$ => {
    // <-- The ExchangeIO function
    // We receive a stream of Operations from `dedupExchange` which
    // we can modify before...
    const forwardOperations$ = operations$;
    // ...calling `forward` with the modified stream. The `forward`
    // function is the next exchange's `ExchangeIO` function, in this
    // case `fetchExchange`.
    const operationResult$ = forward(operations$);
    // We get back `fetchExchange`'s stream of results, which we can
    // also change before returning, which is what `dedupExchange`
    // will receive when calling `forward`.
    return operationResult$;
  };
};

const client = createClient({
  exchanges: [dedupExchange, noopExchange, fetchExchange],
});

https://formidable.com/open-source/urql/docs/concepts/exchanges/

Implementing Authentication

urql has built in support for auth!

https://formidable.com/open-source/urql/docs/api/auth-exchange/

... and an Authentication guide

Caching

Document Cache

Default cache, good for simple applications

Enabling fetching the data from cache instead of querying anew each time

Request policy

import { useQuery } from 'urql';

const [res] = useQuery({
  query: `
    query { todos { id text } }
  `,
  requestPolicy: "cache-and-network",
});

cache-first (default)

cache-and-network, cache-only, and network-only

Cache invalidation

https://www.npmjs.com/package/@urql/exchange-request-policy

import { createClient, dedupExchange, cacheExchange, fetchExchange } from 'urql';
import { requestPolicyExchange } from '@urql/exchange-request-policy';
 
const client = createClient({
  url: 'http://localhost:1234/graphql',
  exchanges: [
    dedupExchange,
    requestPolicyExchange({
      // The amount of time in ms that has to go by before upgrading, default is 5 minutes.
      ttl: 60 * 1000, // 1 minute.
      // An optional function that allows you to specify whether an operation should be upgraded.
      shouldUpgrade: operation => operation.context.requestPolicy !== 'cache-only',
    }),
    cacheExchange,
    fetchExchange,
  ],
});

Graphcache

More sophisticated cache, handles interdependencies!

import { cacheExchange } from "@urql/exchange-graphcache";

const client = new Client({
  exchanges: [dedupExchange, cacheExchange({}), fetchExchange],
});

Handling cache updates after a mutation

const [res] = useQuery({
  query: `
    query { todos { id text } }
  `,
});
const UpdateTodo = `
  mutation ($id: ID!, $title: String!) {
    updateTodo (id: $id, title: $title) {
      id
      title
    }
  }
`;

const Todo = ({ id, title }) => {
  const [updateTodoResult, updateTodo] = useMutation(UpdateTodo);
};

refetchQueries?

in React Apollo

refetchQueries?

manually update cache with mutation result?

export const createPostUpdate = (cache, { data }) => {
  if (!data || !data.createPost) {
    return;
  }
  const createPost = data.createPost;
  const variables = { groupId: createPost.feedItem.data.group_id || undefined };
  const cachedFeed = cache.readQuery({
    query: GET_FEED,
    variables
  });
  const existing = cachedFeed.feed.items.path === createPost.feedItem.path;
  if (!existing) {
    cache.writeQuery({
      query: GET_FEED,
      variables,
      data: {
        ...cachedFeed,
        feed: {
          ...cachedFeed.feed,
          items: [createPost.feedItem, ...cachedFeed.feed.items]
        }
      }
    });
  }
};

refetchQueries?

manually update cache with mutation result?

let the cache handle it?

With Graphcache, the cache is automatically updated with the mutation result

Every piece of data should have a __typename and id

const UpdateTodo = `
  mutation ($id: ID!, $title: String!) {
    updateTodo (id: $id, title: $title) {
      id
      title
    }
  }
`;

If the data doesn't have a reliable id

You can add it manually in the cache config

Or opt out of the cache for these values

const cacheExchangeConfig = {
  keys: {
    SomeFieldWithoutId: data => /* calculate id here based on parent */,
  },
}
const cacheExchangeConfig = {
  keys: {
    SomeFieldWithoutId: () => null,
  },
}

What if the mutation result doesn't return everything that gets updated?

You can also define manual update queries if you need to

import { cacheExchange } from "@urql/exchange-graphcache";

const cacheExchangeConfig = {
  updates: {
    updateToDoListComplete: (
      mutationResult: CompleteToDoType,
      _: any,
      cache: any,
    ) => {
      // if the user completed the last item in their ToDo list, remove it from their profile
      if (mutationResult.updateTodoListComplete.completedPercentage === 1) {
        cache.updateQuery({ query: currentUserQuery }, (data: any) => {
          data.currentUser.currentToDoList = null;
          return data;
        });
      }
    },
  }
}

const client = new Client({
  exchanges: [dedupExchange, cacheExchange(cacheExchangeConfig), fetchExchange],
});

Offline Support with Graphcache

urql devtools

https://github.com/FormidableLabs/urql-devtools

Setup

# npm
npm i @urql/devtools

# yarn
yarn add @urql/devtools

Install the exchange:

Add the exchange to your urql client:

import { createClient, defaultExchanges } from 'urql';
import { devtoolsExchange } from '@urql/devtools';

const client = createClient({
  url: 'http://localhost:3001/graphql',
  exchanges: [devtoolsExchange, ...defaultExchanges],
});
npx urql-devtools

Then to launch the dev tools:

or install the extension on Chrome or Firefox if on the web

Schema explorer

Query / Mutation timeline

Playground

Community

GitHub Discussions

Thank you!

https://formidable.com/open-source/urql

https://github.com/FormidableLabs/urql/discussions

urql community

urql docs

https://github.com/FormidableLabs/urql-devtools

urql devtools

https://slides.com/kadikraman/urql-graphql-madrid/fullscreen

Slides

@kadikraman

Made with Slides.com