Kadi Kraman
@kadikraman
Engineering Manager
Formidable
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
(using Apollo Client)
(using urql)
various RN projects with and without GraphQL
# 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
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>
);
};
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>
));
}
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
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
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/
https://formidable.com/open-source/urql/docs/api/auth-exchange/
Default cache, good for simple applications
Enabling fetching the data from cache instead of querying anew each time
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
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,
],
});
More sophisticated cache, handles interdependencies!
import { cacheExchange } from "@urql/exchange-graphcache";
const client = new Client({
exchanges: [dedupExchange, cacheExchange({}), fetchExchange],
});
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);
};
in React Apollo
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]
}
}
});
}
};
Every piece of data should have a __typename and id
const UpdateTodo = `
mutation ($id: ID!, $title: String!) {
updateTodo (id: $id, title: $title) {
id
title
}
}
`;
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,
},
}
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],
});
https://github.com/FormidableLabs/urql-devtools
# 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
or install the extension on Chrome or Firefox if on the web
Schema explorer
Query / Mutation timeline
Playground
GitHub Discussions
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