Skip to content

Commit 8204639

Browse files
Add PartialData and Empty constructors. (#196)
1 parent 2b5877f commit 8204639

File tree

17 files changed

+250
-125
lines changed

17 files changed

+250
-125
lines changed

__tests__/Types_test.re

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,81 @@ describe("Types", () => {
4141
);
4242
});
4343

44+
it(
45+
"should return PartialData constructor if the GraphQL API responded with data and an error",
46+
() => {
47+
let graphQLError =
48+
GraphQLError.{
49+
message: "Error encountered querying field dogs.",
50+
locations: Some([|{line: 2, column: 17}|]),
51+
path: None,
52+
source: None,
53+
nodes: None,
54+
positions: None,
55+
originalError: None,
56+
extensions: None,
57+
};
58+
59+
let errorJs = {
60+
"message": "Error returned by GraphQL API",
61+
"graphQLErrors": [|graphQLError|],
62+
"networkError": Js.Nullable.null,
63+
"response": Js.Nullable.null,
64+
};
65+
66+
let error =
67+
CombinedError.{
68+
message: "Error returned by GraphQL API",
69+
graphQLErrors: [|graphQLError|],
70+
networkError: None,
71+
response: None,
72+
};
73+
74+
let response =
75+
Types.{
76+
fetching: false,
77+
data: Some(Js.Json.string("Hello")),
78+
error: Some(errorJs),
79+
extensions: None,
80+
stale: false,
81+
};
82+
83+
let parse = json => Js.Json.decodeString(json);
84+
let result = Types.urqlResponseToReason(~response, ~parse);
85+
86+
Expect.(
87+
expect(result.response)
88+
|> toEqual(Types.PartialData(Some("Hello"), error.graphQLErrors))
89+
);
90+
},
91+
);
92+
4493
it(
4594
"should return Error constructor if the GraphQL API responded with an error",
4695
() => {
96+
let graphQLError =
97+
GraphQLError.{
98+
message: "Error encountered querying field dogs.",
99+
locations: Some([|{line: 2, column: 17}|]),
100+
path: None,
101+
source: None,
102+
nodes: None,
103+
positions: None,
104+
originalError: None,
105+
extensions: None,
106+
};
107+
47108
let errorJs = {
48109
"message": "Error returned by GraphQL API",
49-
"graphQLErrors": Js.Nullable.null,
110+
"graphQLErrors": [|graphQLError|],
50111
"networkError": Js.Nullable.null,
51112
"response": Js.Nullable.null,
52113
};
53114

54115
let error =
55116
CombinedError.{
56117
message: "Error returned by GraphQL API",
57-
graphQLErrors: None,
118+
graphQLErrors: [|graphQLError|],
58119
networkError: None,
59120
response: None,
60121
};
@@ -73,7 +134,7 @@ describe("Types", () => {
73134
Expect.(expect(result.response) |> toEqual(Types.Error(error)));
74135
});
75136

76-
it("should return NotFound constructor if none of the above apply", () => {
137+
it("should return Empty constructor if none of the above apply", () => {
77138
let response =
78139
Types.{
79140
fetching: false,
@@ -85,7 +146,7 @@ describe("Types", () => {
85146
let parse = _json => ();
86147
let result = Types.urqlResponseToReason(~response, ~parse);
87148

88-
Expect.(expect(result.response) |> toEqual(Types.NotFound));
149+
Expect.(expect(result.response) |> toEqual(Types.Empty));
89150
});
90151
})
91152
});

docs/advanced.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Advanced
2+
3+
Curious to know more about the internals of `reason-urql`? You've come to the right place!
4+
5+
## The `response` variant
6+
7+
`reason-urql` uses a special `variant` called `response` to make pattern matching on the results you get from your GraphQL API as easy as possible. In JS land, users have to do something like the following:
8+
9+
```js
10+
// Inside a React component
11+
const [result] = useQuery({ query, variables });
12+
13+
if (result.fetching) {
14+
return <Loading />; // Some fetching UI.
15+
} else if (result.error) {
16+
return <Error error={result.error} />; // Some error UI.
17+
}
18+
19+
return <Data data={result.data} />; // Some data UI.
20+
```
21+
22+
This is fine and works great for JS users, but we have pattern matching in Reason! Enter `reason-urql`'s `response` variant. `response` is a single variant with five constructors:
23+
24+
```reason
25+
type response('response) {
26+
| Fetching
27+
| Data('response)
28+
| PartialData('response, GraphQLError.t)
29+
| Error(CombinedError.t)
30+
| Empty;
31+
}
32+
```
33+
34+
This variant is always available on the record returned by `reason-urql`'s hooks. Let's go over what each constructor (or "tag") means and when you'd want to pattern match on each.
35+
36+
### The `Fetching` constructor
37+
38+
`Fetching` refers to when your query, mutation, or subscription is fetching data but has not yet received a response. For `useQuery` and `useMutation` this will turn `true` when the initial request has been fired off, and turn to `false` once data (or an error) has been received. Note that for `useSubscription`, `fetching` will always be `true` as long as the subcription is active. For this case, the `Data` constructor will take precedence even while `fetching` is still `true`.
39+
40+
### The `Data` constructor
41+
42+
`Data(d)` refers to when your query, mutation, or subscription has received data from your GraphQL API. It takes a single argument, `d`, that represents the actual data returned by your GraphQL API. You can then operate on `d` however you like to return UI.
43+
44+
### The `PartialData` constructor
45+
46+
`PartialData(d, e)` is quite similar to `Data` with one major difference – it represents the case where **both** data and errors were returned by your GraphQL API. This can happen if data resolves fine for parts of your query or mutation but fails to resolve for others. The type of `e` is our binding to `GraphQLError`, which gives you access to the message, source locations, and extensions of the error returned by your GraphQL server.
47+
48+
### The `Error` constructor
49+
50+
`Error(e)` refers to when your query, mutation, or subscription has encountered an error in the course of execution. It takes a single argument, `e`, that represents the [`CombinedError`](https://formidable.com/open-source/urql/docs/basics/errors/) returned by `urql`. `CombinedError` will return a record containing `message`, `networkError`, `graphQLErrors`, and `response`, allowing you to subsequently render different UI based on the _types_ of errors you encountered.
51+
52+
### The `Empty` constructor
53+
54+
The `Empty` constructor is reserved for the rare case where `reason-urql` has made a request to your GraphQL API but neither `data` nor `errors` were returned. In practice, this case is very rare. It may occur if your user goes offline unexpectedly or if your GraphQL API returns `undefined` in cases where no data matches a particular resolver. Think of it as your fallback case.

docs/client-and-provider.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ Imperatively execute a GraphQL query operation.
115115
| `pollInterval` | `int=?` | Optional. Instructs the client to reexecute the query every `pollInterval` milliseconds. |
116116
| `meta` | `Types.operationDebugMeta=?` | Optional. Add metadata that is only available in development with devtools. |
117117

118-
`client.executeQuery` returns a [`wonka` `source`](https://wonka.kitten.sh/api/sources) containing the results of executing the query. The result record has a variant type called `response` with constructors for `Data(d)`, `Error(e)`, and `NotFound`, in addition to `data` and `error` fields for accessing the raw response values if desired.
118+
`client.executeQuery` returns a [`wonka` `source`](https://wonka.kitten.sh/api/sources) containing the results of executing the query. The result record has a variant type called `response` with constructors for `Data(d)`, `Error(e)`, and `Empty`, in addition to `data` and `error` fields for accessing the raw response values if desired.
119119

120120
| Return | Description |
121121
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
@@ -145,7 +145,7 @@ Client.executeQuery(~client, ~request=GetAllDogs.make(), ())
145145
switch(Client.(data.response)) {
146146
| Data(d) => /* Access data returned from executing the request. */
147147
| Error(e) => /* Access any errors returned from executing the request. */
148-
| NotFound => /* Fallback if neither Data nor Error return information. */
148+
| Empty => /* Fallback if neither Data nor Error return information. */
149149
}
150150
});
151151
```
@@ -164,7 +164,7 @@ The same as `Client.executeQuery`, but returns a `Js.Promise.t` rather than a `w
164164
| `pollInterval` | `int=?` | Optional. Instructs the client to reexecute the query every `pollInterval` milliseconds. |
165165
| `meta` | `Types.operationDebugMeta=?` | Optional. Add metadata that is only available in development with devtools. |
166166

167-
`Client.query` returns a `Js.Promise.t` containing the results of executing the query. The result record has a variant type called `response` with constructors for `Data(d)`, `Error(e)`, and `NotFound`, in addition to `data` and `error` fields for accessing the raw response values if desired.
167+
`Client.query` returns a `Js.Promise.t` containing the results of executing the query. The result record has a variant type called `response` with constructors for `Data(d)`, `Error(e)`, and `Empty`, in addition to `data` and `error` fields for accessing the raw response values if desired.
168168

169169
| Return | Description |
170170
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
@@ -194,7 +194,7 @@ Client.query(~client, ~request=GetAllDogs.make(), ())
194194
switch(Client.(data.response)) {
195195
| Data(d) => /* Access data returned from executing the request. */
196196
| Error(e) => /* Access any errors returned from executing the request. */
197-
| NotFound => /* Fallback if neither Data nor Error return information. */
197+
| Empty => /* Fallback if neither Data nor Error return information. */
198198
}
199199
});
200200
```
@@ -213,7 +213,7 @@ Execute a GraphQL mutation operation.
213213
| `pollInterval` | `int=?` | Optional. Instructs the client to reexecute the mutation every `pollInterval` milliseconds. |
214214
| `meta` | `Types.operationDebugMeta=?` | Optional. Add metadata that is only available in development with devtools. |
215215

216-
`Client.executeMutation` returns a [`wonka` `source`](https://wonka.kitten.sh/api/sources) containing the results of executing the mutation. The result record has a variant type called `response` with constructors for `Data(d)`, `Error(e)`, and `NotFound`, in addition to `data` and `error` fields for accessing the raw response values if desired.
216+
`Client.executeMutation` returns a [`wonka` `source`](https://wonka.kitten.sh/api/sources) containing the results of executing the mutation. The result record has a variant type called `response` with constructors for `Data(d)`, `Error(e)`, and `Empty`, in addition to `data` and `error` fields for accessing the raw response values if desired.
217217

218218
| Return | Description |
219219
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -243,7 +243,7 @@ Client.executeMutation(~client, ~request=LikeDog.make(~key="VmeRTX7j-", ()), ())
243243
switch(Client.(data.response)) {
244244
| Data(d) => /* Access data returned from executing the request. */
245245
| Error(e) => /* Access any errors returned from executing the request. */
246-
| NotFound => /* Fallback if neither Data nor Error return information. */
246+
| Empty => /* Fallback if neither Data nor Error return information. */
247247
}
248248
});
249249
```
@@ -262,7 +262,7 @@ The same as `Client.executeMutation`, but returns a `Js.Promise.t` rather than a
262262
| `pollInterval` | `int=?` | Optional. Instructs the client to reexecute the mutation every `pollInterval` milliseconds. |
263263
| `meta` | `Types.operationDebugMeta=?` | Optional. Add metadata that is only available in development with devtools. |
264264

265-
`Client.mutation` returns a `Js.Promise.t` containing the results of executing the mutation. The result record has a variant type called `response` with constructors for `Data(d)`, `Error(e)`, and `NotFound`, in addition to `data` and `error` fields for accessing the raw response values if desired.
265+
`Client.mutation` returns a `Js.Promise.t` containing the results of executing the mutation. The result record has a variant type called `response` with constructors for `Data(d)`, `Error(e)`, and `Empty`, in addition to `data` and `error` fields for accessing the raw response values if desired.
266266

267267
| Return | Description |
268268
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
@@ -292,7 +292,7 @@ Client.mutation(~client, ~request=LikeDog.make(~key="VmeRTX7j-", ()), ())
292292
switch(Client.(data.response)) {
293293
| Data(d) => /* Access data returned from executing the request. */
294294
| Error(e) => /* Access any errors returned from executing the request. */
295-
| NotFound => /* Fallback if neither Data nor Error return information. */
295+
| Empty => /* Fallback if neither Data nor Error return information. */
296296
}
297297
});
298298
```
@@ -332,7 +332,7 @@ Client.executeSubscription(~client, ~request=SubscribeMessages.make(), ())
332332
switch(Client.(data.response)) {
333333
| Data(d) => /* Access data returned from executing the request. */
334334
| Error(e) => /* Access any errors returned from executing the request. */
335-
| NotFound => /* Fallback if neither Data nor Error return information. */
335+
| Empty => /* Fallback if neither Data nor Error return information. */
336336
}
337337
});
338338
```

docs/getting-started.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ let make = () => {
5959
let (Hooks.{ response }, executeQuery) = Hooks.useQuery(~request, ());
6060
6161
/* Pattern match on the response variant.
62-
This variant has constructors for Fetching, Data(d), Error(e), and NotFound. */
62+
This variant has constructors for Fetching, Data(d), PartialData(d, e) Error(e), and Empty. */
6363
switch (response) {
6464
| Fetching => <LoadingSpinner />
65-
| Data(d) => {
65+
| Data(d)
66+
| PartialData(d, _) => {
6667
Array.map(dog =>
6768
<div key=dog##key>
6869
<span> {j|$dog##name $dog##likes|j}->React.string </span>
@@ -76,7 +77,7 @@ let make = () => {
7677
| Some(_e) => <div> "Network Error"->React.string </div>
7778
| None => <div> "Other Error"->React.string </div>
7879
}
79-
| NotFound => <div> "Something went wrong!"->React.string </div>
80+
| Empty => <div> "Something went wrong!"->React.string </div>
8081
}
8182
}
8283
```

0 commit comments

Comments
 (0)