UNPKG

9.98 kBMarkdownView Raw
1This package is **opinionated Relay wrapper** used at Kiwi.com. Goal of this package is to create powerful yet simple to use Relay wrapper with all the important features:
2
3- query logging during development
4- network fetching with retries and timeouts (see [`@kiwicom/fetch`](https://github.com/kiwicom/fetch))
5- support for uploadables
6- request burst cache (response cache)
7- stored operations (known as persistent queries)
8- correct Relay environment context handling
9
10More info about Relay, prior art:
11
12- [Relay Docs](https://relay.dev/docs/en/introduction-to-relay.html)
13- [Relay Modern Network Deep Dive](https://medium.com/entria/relay-modern-network-deep-dive-ec187629dfd3)
14- [Advanced Relay topics](https://github.com/mrtnzlml/meta/blob/master/relay.md)
15- [Relay Example](https://github.com/kiwicom/relay-example)
16
17# Install
18
19**Please read this carefully.**
20
21Before you start you should uninstall _all_ the Relay related packages you installed manually (Relay runtime, compiler, `react-relay` and `babel-plugin-relay`). You should also remove custom `flow-typed` definitions for Relay. This package takes care about everything you need.
22
23```text
24yarn add react graphql @kiwicom/relay
25```
26
27# Usage
28
29Usage is the same as with original Relay: first you should setup [Relay babel plugin](https://relay.dev/docs/en/installation-and-setup#set-up-babel-plugin-relay) and then [Relay compiler](https://relay.dev/docs/en/installation-and-setup#set-up-relay-compiler) (we prefer our own Compiler implementation, see below). It's important to note that the only package related to Relay you need to install is `@kiwicom/relay`. It contains all the necessary dependencies.
30
31Minimal `.babelrc` file:
32
33```json
34{
35 "plugins": ["relay"]
36}
37```
38
39Minimal Relay compiler script in `package.json` (see below how to download the GraphQL schema):
40
41```json
42{
43 "scripts": {
44 "relay": "kiwicom-relay-compiler --src=./src --schema=./schema.graphql"
45 }
46}
47```
48
49In the previous example we used `--schema` in order to use Relay compiler. This package provides a way how to download it easily:
50
51```text
52$ yarn kiwicom-fetch-schema --help
53
54Usage: fetch-schema [options]
55
56Options:
57 --resource <url> (default: "https://graphql.kiwi.com/")
58 --filename <path> (default: "schema.graphql")
59 -h, --help output usage information
60```
61
62There are a few additional rules to make sure everything goes smoothly:
63
64- you should always use `@kiwicom/relay` package and never Relay dependencies directly
65- do not import internals of this package (no `@kiwicom/relay/something/private.js`)
66- please contact us directly in case something is problematic
67
68Please continue reading to discover `@kiwicom/relay` specifics.
69
70# Minimal example
71
72```js
73import * as React from 'react';
74import { graphql, QueryRenderer } from '@kiwicom/relay';
75import type { AppQueryResponse } from './__generated__/AppQuery.graphql';
76
77function handleResponse(props: AppQueryResponse) {
78 const edges = props.allLocations?.edges ?? [];
79 return (
80 <ol>
81 {edges.map(edge => (
82 <li key={edge?.node?.id}>{edge?.node?.name}</li>
83 ))}
84 </ol>
85 );
86}
87
88export default function App(props) {
89 return (
90 <QueryRenderer
91 // Following `clientID` helps us to identify who is sending the request and it's
92 // required unless you provide custom `environment` (see below).
93 clientID="unique-client-identification"
94 query={graphql`
95 query AppQuery {
96 allLocations(first: 20) {
97 edges {
98 node {
99 id
100 name
101 }
102 }
103 }
104 }
105 `}
106 onSystemError={({ error, retry }) => console.error(error)} // optional (Sentry maybe?)
107 onLoading={() => <div>Loading...</div>} // optional
108 onResponse={handleResponse}
109 />
110 );
111}
112```
113
114This API is high-level on purpose but it's possible to decompose it when you need something more advanced (custom `Environment` for example). However, even the decomposed parts are still very opinionated and new features are being unlocked only when necessary.
115
116# Detailed info
117
118## Relay Compiler
119
120We use our own `kiwicom-relay-compiler` which adds some additional features:
121
122- it outputs ES6 modules
123- it warns when you use deprecated fields in your queries and fragments
124- it supports several implementations of persistent queries
125
126_Missing some info in docs? Please send a merge request._
127
128## Environment
129
130The default `QueryRenderer` falls back to querying [graphql.kiwi.com](https://graphql.kiwi.com) if custom environment is not specified. This helps you to start faster but you have to specify `clientID` to identify the requests. You can also create your own environment:
131
132```js
133import { createEnvironment, createNetworkFetcher } from '@kiwicom/relay';
134
135const Environment = createEnvironment({
136 fetchFn: createNetworkFetcher('https://graphql.kiwi.com', {
137 'X-Client': '** TODO **',
138 }),
139 // subscribeFn
140 // graphiQLPrinter (see below)
141});
142```
143
144This way you can change the URL or provide additional headers (`X-Client` is still required in `createNetworkFetcher`). You can even replace the whole network fetcher if you want (not recommended). As you can see the high-level API decomposes nicely: it allows you to start quickly with a solid base but you can also use very specific features to your application if you want to. But please, consider if these features could be beneficial even for other users and maybe contribute them back.
145
146Now, just use the created environment:
147
148```js
149export default function App() {
150 return (
151 <QueryRenderer
152 environment={Environment} // <<<
153 query={graphql` ... `}
154 onResponse={handleResponse}
155 />
156 );
157}
158```
159
160There is also a `RelayEnvironmentProvider` component which allows you to provide the `Environment` easily for the whole application:
161
162```js
163function render() {
164 return (
165 <RelayEnvironmentProvider environment={Environment}>
166 {/* your React application here */}
167 </RelayEnvironmentProvider>
168 );
169}
170```
171
172Query Renderer itself also behaves as an environment provider and it will reuse the environment from `RelayEnvironmentProvider` if you used it in the root of your application.
173
174### Tip: do not expose global `Environment`
175
176You should never import your custom environment directly when working with mutations or subscriptions. Always use the environment provided in the props (exposed by any Relay container):
177
178```js
179import {
180 type RelayProp, // or `PaginationRelayProp` or `RefetchRelayProp` types
181} from '@kiwicom/relay';
182
183type Props = {| +relay: RelayProp |};
184
185function Component(props: Props) {
186 useEffect(() => {
187 commitMutation(props.relay.environment, { mutation: graphql` ... ` });
188 });
189}
190```
191
192Only this way you can be sure that your mutation/subscription is using correct environment. This common bug is usually not very visible but it's very serious when you have multiple environments in your application.
193
194How to get environment when your component needs it for mutations for example and there is no `props.relay`? Simply use `useRelayEnvironment` hook. This hook can be used anywhere below Query Renderer or `RelayEnvironmentProvider` component in the React tree:
195
196```js
197import { useRelayEnvironment } from '@kiwicom/relay';
198
199function Component() {
200 const environment = useRelayEnvironment();
201 // TODO: do something with the env
202}
203```
204
205### Tip: enable GraphiQL printer
206
207You can enable GraphiQL printer via `graphiQLPrinter` parameter when creating the environment. It will enhance your Relay console logs with link to your GraphiQL:
208
209![GraphiQL printer](./graphiql-printer.png)
210
211You just have to create a function which returns link to your GraphiQL, for example:
212
213```js
214const GraphiQLPrinter = (request, variables) => {
215 return `https://graphql.kiwi.com/?query=${encodeURIComponent(
216 request.text,
217 )}&variables=${encodeURIComponent(JSON.stringify(variables))}`;
218};
219```
220
221## Query Renderer
222
223Query renderer behaves similarly to the default one in Relay except it exposes some additional high level API. It's certainly possible to override the and `render` property just like `environment`. However, please note that `render` property has priority over `onSystemError`, `onLoading` and `onResponse`. It's not recommended to use it unless you need something really special because these preferred 3 props solve majority of the cases.
224
225```js
226export default function App() {
227 return (
228 <QueryRenderer
229 environment={Environment}
230 query={graphql` ... `}
231 render={({ error, props, retry }) => {
232 /* TODO */
233 }}
234 />
235 );
236}
237```
238
239### Tip: use custom `QueryRenderer` wrapper
240
241It's a good idea to create a custom wrapper of the `QueryRenderer` so you don't have to copy-paste it everywhere. This could be your new API (no loading, system error handlers or client identification):
242
243```js
244export default function App() {
245 return (
246 <CustomQueryRenderer
247 query={graphql`
248 query AppQuery {
249 ...AllLocations_data
250 }
251 `}
252 render={props => null} // TODO (handleResponse)
253 />
254 );
255}
256```
257
258## Refetch container
259
260Refetch container is the best when you are changing variables in the component fragment or just simply refetching. Typical example is search or bi-directional pagination. Simply import the HoC as well as the refetch Flow type:
261
262```js
263import { graphql, createRefetchContainer, type RefetchRelayProp } from '@kiwicom/relay';
264```
265
266Usage:
267
268```js
269export default createRefetchContainer(
270 Component,
271 /* TODO: refetch fragments */,
272 /* TODO: refetch query */
273);
274```
275
276And later you can call the refetch function:
277
278```js
279function loadMore() {
280 // property `relay` should be annotated with the `RefetchRelayProp` type
281 props.relay.refetch(/* TODO: refetchVariables */);
282}
283```
284
285Similar rules apply to pagination container which solves one particular use-case of refetch container: "load more" pagination. The difference is that you have to use `type PaginationRelayProp` instead.