1---
2title: UI-Patterns
3order: 6
4---
5
6# UI Patterns
7
8> This page is incomplete. You can help us expanding it by suggesting more patterns or asking us about common problems you're facing on [GitHub Discussions](https://github.com/urql-graphql/urql/discussions).
9
10Generally, `urql`'s API surface is small and compact. Some common problems that we're facing when building apps may look like they're not a built-in feature, however, there are several patterns that even a lean UI can support.
11This page is a collection of common UI patterns and problems we may face with GraphQL and how we can tackle them in
12`urql`. These examples will be written in React but apply to any other framework.
13
14## Infinite scrolling
15
16"Infinite Scrolling" is the approach of loading more data into a page's list without splitting that list up across multiple pages.
17
18There are a few ways of going about this. In our [normalized caching chapter on the topic](../graphcache/local-resolvers.md#pagination)
19we see an approach with `urql`'s normalized cache, which is suitable to get started quickly. However, this approach also requires some UI code as well to keep track of pages.
20Let's have a look at how we can create a UI implementation that makes use of this normalized caching feature.
21
22```js
23import React from 'react';
24import { useQuery, gql } from 'urql';
25
26const PageQuery = gql`
27 query Page($first: Int!, $after: String) {
28 todos(first: $first, after: $after) {
29 nodes {
30 id
31 name
32 }
33 pageInfo {
34 hasNextPage
35 endCursor
36 }
37 }
38 }
39`;
40
41const SearchResultPage = ({ variables, isLastPage, onLoadMore }) => {
42 const [{ data, fetching, error }] = useQuery({ query: PageQuery, variables });
43 const todos = data?.todos;
44
45 return (
46 <div>
47 {error && <p>Oh no... {error.message}</p>}
48 {fetching && <p>Loading...</p>}
49 {todos && (
50 <>
51 {todos.nodes.map(todo => (
52 <div key={todo.id}>
53 {todo.id}: {todo.name}
54 </div>
55 ))}
56 {isLastPage && todos.pageInfo.hasNextPage && (
57 <button onClick={() => onLoadMore(todos.pageInfo.endCursor)}>load more</button>
58 )}
59 </>
60 )}
61 </div>
62 );
63};
64
65const Search = () => {
66 const [pageVariables, setPageVariables] = useState([
67 {
68 first: 10,
69 after: '',
70 },
71 ]);
72
73 return (
74 <div>
75 {pageVariables.map((variables, i) => (
76 <SearchResultPage
77 key={'' + variables.after}
78 variables={variables}
79 isLastPage={i === pageVariables.length - 1}
80 onLoadMore={after => setPageVariables([...pageVariables, { after, first: 10 }])}
81 />
82 ))}
83 </div>
84 );
85};
86```
87
88Here we keep an array of all `variables` we've encountered and use them to render their
89respective `result` page. This only rerenders the additional page rather than having a long
90list that constantly changes. [You can find a full code example of this pattern in our example folder on the topic of pagination.](https://github.com/urql-graphql/urql/tree/main/examples/with-pagination)
91
92This code doesn't take changing variables into account, which will affect the cursors. For an
93example that takes full infinite scrolling into account, [you can find a full code example of an
94extended pattern in our example folder on the topic of infinite pagination.](https://github.com/urql-graphql/urql/tree/main/examples/with-infinite-pagination)
95
96## Prefetching data
97
98We sometimes find it necessary to load data for a new page before that page is opened, for instance while a JS bundle is still loading. We may
99do this with help of the `Client`, by calling methods without using the React bindings directly.
100
101```js
102import React from 'react';
103import { useClient, gql } from 'urql';
104
105const TodoQuery = gql`
106 query Todo($id: ID!) {
107 todo(id: $id) {
108 id
109 name
110 }
111 }
112`;
113
114const Component = () => {
115 const client = useClient();
116 const router = useRouter();
117
118 const transitionPage = React.useCallback(async id => {
119 const loadJSBundle = import('./page.js');
120 const loadData = client.query(TodoQuery, { id }).toPromise();
121 await Promise.all([loadJSBundle, loadData]);
122 router.push(`/todo/${id}`);
123 }, []);
124
125 return <button onClick={() => transitionPage('1')}>Go to todo 1</button>;
126};
127```
128
129Here we're calling `client.query` to prepare a query when the transition begins.
130We then call `toPromise()` on this query which activates it. Our `Client` and its cache share results, which means that we've already kicked off or even completed the query before we're on the new page.
131
132## Lazy query
133
134It's often required to "lazily" start a query, either at a later point or imperatively. This means that we don't start a query when a new component is mounted immediately.
135
136Parts of `urql` that automatically start, like the `useQuery` hook, have a concept of a [`pause` option.](./react-preact.md#pausing-usequery) This option is used to prevent the hook from automatically starting a new query.
137
138```js
139import React from 'react';
140import { useQuery, gql } from 'urql';
141
142const TodoQuery = gql`
143 query Todos {
144 todos {
145 id
146 name
147 }
148 }
149`;
150
151const Component = () => {
152 const [result, fetch] = useQuery({ query: TodoQuery, pause: true });
153 const router = useRouter();
154
155 return <button onClick={fetch}>Load todos</button>;
156};
157```
158
159We can unpause the hook to start fetching, or, like in this example, call its returned function to manually kick off the query.
160
161## Reacting to focus and stale time
162
163In urql we leverage our extensibility pattern named "Exchanges" to manipulate the way
164data comes in and goes out of our client.
165
166- [Stale time](https://github.com/urql-graphql/urql/tree/main/exchanges/request-policy)
167- [Focus](https://github.com/urql-graphql/urql/tree/main/exchanges/refocus)
168
169When we want to introduce one of these patterns we add the package and add it to the `exchanges`
170property of our `Client`. In the case of these two we'll have to add it before the cache
171else our requests will never get upgraded.
172
173```js
174import { Client, cacheExchange, fetchExchange } from 'urql';
175import { refocusExchange } from '@urql/exchange-refocus';
176
177const client = new Client({
178 url: 'some-url',
179 exchanges: [refocusExchange(), cacheExchange, fetchExchange],
180});
181```
182
183That's all we need to do to react to these patterns.