Skip to content

Commit b311a61

Browse files
authored
docs: Add subscriptions to colocate (#1175)
1 parent 3310135 commit b311a61

File tree

13 files changed

+244
-58
lines changed

13 files changed

+244
-58
lines changed

docs/api/Delete.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function MyTable() {
8282
}
8383
```
8484

85-
### Usage with useResource()
85+
### Impact on useResource()
8686

8787
When entities are deleted in a result currently being presented in React, useResource()
8888
will consider them invalid

docs/api/Endpoint.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ Package: [@rest-hooks/endpoint](https://www.npmjs.com/package/@rest-hooks/endpoi
8484
Members double as options (second constructor arg). While none are required, the first few
8585
have defaults.
8686

87-
### key: (params) => string
87+
### key: (params) => string {#key}
8888

8989
Serializes the parameters. This is used to build a lookup key in global stores.
9090

@@ -94,13 +94,13 @@ Default:
9494
`${this.fetch.name} ${JSON.stringify(params)}`;
9595
```
9696

97-
### sideEffect: true | undefined
97+
### sideEffect: true | undefined {#sideeffect}
9898

9999
Used to indicate endpoint might have side-effects (non-idempotent). This restricts it
100100
from being used with [useResource()](./useresource) or [useRetrieve()](useRetrieve) as those can hit the
101101
endpoint an unpredictable number of times.
102102

103-
### schema: Schema
103+
### schema: Schema {#schema}
104104

105105
Declarative definition of how to [process responses](./schema)
106106

@@ -126,7 +126,7 @@ const UserDetail = new Endpoint(
126126
);
127127
```
128128

129-
### extend(EndpointOptions): Endpoint
129+
### extend(EndpointOptions): Endpoint {#extend}
130130

131131
Can be used to further customize the endpoint definition
132132

docs/api/Entity.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,12 @@ pk() {
110110
}
111111
```
112112
113-
### static get key(): string
113+
### static get key(): string {#key}
114114
115115
This defines the key for the Entity itself, rather than an instance. This needs to be a globally
116116
unique value.
117117
118-
### static merge(existing, incoming): mergedValue
118+
### static merge(existing, incoming): mergedValue {#merge}
119119
120120
```typescript
121121
static merge<T extends typeof SimpleRecord>(existing: InstanceType<T>, incoming: InstanceType<T>) => InstanceType<T>
@@ -149,7 +149,7 @@ class LatestPriceEntity extends Entity {
149149
}
150150
```
151151
152-
### static indexes?: (keyof this)[]
152+
### static indexes?: (keyof this)[] {#indexes}
153153
154154
Indexes enable increased performance when doing lookups based on those parameters. Add
155155
fieldnames (like `slug`, `username`) to the list that you want to send as params to lookup
@@ -223,7 +223,7 @@ Nested below:
223223
const price = useCache(LatestPrice, { symbol: 'BTC' });
224224
```
225225
226-
### static schema: { [k: keyof this]: Schema }
226+
### static schema: { [k: keyof this]: Schema } {#schema}
227227
228228
Set this to [define entities nested](../guides/nested-response) inside this one.
229229

docs/api/Manager.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,35 @@ this.middleware = ({ dispatch, getState }) => (next) => async (action) => {
7878
### Middleware data stream
7979

8080
```typescript
81+
import type { Manager } from '@rest-hooks/core';
8182
import { createReceive } from '@rest-hooks/core';
8283

83-
this.middleware = ({ dispatch, getState }) => {
84-
this.websocket.onmessage = (event) => {
85-
dispatch(
86-
createReceive(event.data, { schema: this.Schemas[event.type] })
87-
);
84+
export default class StreamManager implements Manager
85+
{
86+
protected declare middleware: Middleware;
87+
protected declare websocket: Websocket;
88+
89+
constructor(url: string) {
90+
this.websocket = new Websocket(url);
91+
92+
// highlight-start
93+
this.middleware = ({ dispatch, getState }) => {
94+
this.websocket.onmessage = (event) => {
95+
dispatch(
96+
createReceive(event.data, { schema: this.Schemas[event.type] })
97+
);
98+
}
99+
return (next) => async (action) => next(action);
100+
}
101+
// highlight-end
102+
}
103+
104+
cleanup() {
105+
this.websocket.close();
106+
}
107+
108+
getMiddleware<T extends StreamManager>(this: T) {
109+
return this.middleware;
88110
}
89-
return (next) => async (action) => next(action);
90111
}
91112
```

docs/getting-started/data-dependency.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,70 @@ export default function TodoDetail({ id }: { id: number }) {
182182
```
183183

184184
Read more about [useStatefulResource](../guides/no-suspense)
185+
186+
## Subscriptions
187+
188+
When data is likely to change due to external factor; [useSubscription()](../api/useSubscription.md)
189+
ensures continual updates while a component is mounted.
190+
191+
<Tabs
192+
defaultValue="Single"
193+
values={[
194+
{ label: 'Single', value: 'Single' },
195+
{ label: 'List', value: 'List' },
196+
]}>
197+
<TabItem value="Single">
198+
199+
```tsx
200+
import { useResource } from 'rest-hooks';
201+
// local directory for API definitions
202+
import { todoDetail } from 'endpoints/todo';
203+
204+
export default function TodoDetail({ id }: { id: number }) {
205+
const todo = useResource(todoDetail, { id });
206+
// highlight-next-line
207+
useSubscription(todoDetail, { id });
208+
return <div>{todo.title}</div>;
209+
}
210+
```
211+
212+
</TabItem>
213+
<TabItem value="List">
214+
215+
```tsx
216+
import { useResource } from 'rest-hooks';
217+
// local directory for API definitions
218+
import { todoList } from 'endpoints/todo';
219+
220+
export default function TodoList() {
221+
const todos = useResource(todoList, {});
222+
// highlight-next-line
223+
useSubscription(todoList, {});
224+
return (
225+
<section>
226+
{todos.map(todo => (
227+
<div key={todo.id}>{todo.title}</div>
228+
))}
229+
</section>
230+
);
231+
}
232+
```
233+
234+
</TabItem>
235+
</Tabs>
236+
237+
Subscriptions are orchestrated by [Managers](../api/Manager.md). Out of the box,
238+
polling based subscriptions can be used by adding [pollFrequency](../api/Endpoint.md#pollfrequency-number) to an endpoint.
239+
For pushed based networking protocols like websockets, see the [example websocket stream manager](../api/Manager.md#middleware-data-stream).
240+
241+
```typescript
242+
const fetchTodoDetail = ({ id }) =>
243+
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(res =>
244+
res.json(),
245+
);
246+
const todoDetail = new Endpoint(
247+
fetchTodoDetail,
248+
// highlight-next-line
249+
{ pollFrequency: 1000 },
250+
);
251+
```

docs/getting-started/entity.md

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
title: Entity and Data Normalization
33
sidebar_label: Entity
44
---
5+
56
import Tabs from '@theme/Tabs';
67
import TabItem from '@theme/TabItem';
78
import LanguageTabs from '@site/src/components/LanguageTabs';
@@ -103,12 +104,24 @@ export function PresentationsPage() {
103104
</TabItem>
104105
</Tabs>
105106

107+
Extracting entities from a response is known as `normalization`. Accessing a response reverses
108+
the process via `denormalization`.
109+
110+
:::info Global Referential Equality
111+
106112
Using entities expands Rest Hooks' global referential equality guarantee beyond the granularity of
107113
an entire endpoint response.
108114

115+
:::
116+
109117
## Mutations and Dynamic Data
110118

111-
Be sure to include *any* data that has changed in your response.
119+
When an endpoint changes data, this is known as a [side effect](../guides/rpc.md). Marking an endpoint with [sideEffect: true](../api/Endpoint.md#sideeffect)
120+
tells Rest Hooks that this endpoint is not idempotent, and thus should not be allowed in hooks
121+
that may call the endpoint an arbitrary number of times like [useResource()](../api/useResource.md) or [useRetrieve()](../api/useRetrieve.md)
122+
123+
By including the changed data in the endpoint's response, Rest Hooks is able to able to update
124+
any entities it extracts by specifying the schema.
112125

113126
<Tabs
114127
defaultValue="Create"
@@ -223,8 +236,11 @@ export default function TodoWithDelete({ todo }: { todo: Todo }) {
223236
</TabItem>
224237
</Tabs>
225238

239+
:::info
240+
226241
Mutations automatically update the normalized cache, resulting in consistent and fresh data.
227242

243+
:::
228244

229245
## Schema
230246

@@ -234,24 +250,100 @@ Schemas are a declarative definition of how to [process responses](./schema)
234250
- Classes to [deserialize fields](../guides/network-transform#deserializing-fields)
235251

236252
```typescript
237-
import { Endpoint, Entity } from '@rest-hooks/endpoint';
253+
import { Endpoint } from '@rest-hooks/endpoint';
254+
255+
const fetchTodoList = (params: any) =>
256+
fetch(`https://jsonplaceholder.typicode.com/todos/`).then(res => res.json());
257+
258+
const todoList = new Endpoint(fetchTodoList, {
259+
// highlight-next-line
260+
schema: [Todo],
261+
sideEffect: true,
262+
});
263+
```
264+
265+
Placing our [Entity](../api/Entity.md) `Todo` in an array, tells Rest Hooks to expect
266+
an array of `Todos`.
267+
268+
Aside from array, there are a few more 'schemas' provided for various patterns. The first two (Object and Array)
269+
have shorthands of using object and array literals.
270+
271+
- [Object](../api/Object.md): maps with known keys
272+
- [Array](../api/Array.md): variably sized arrays
273+
- [Union](../api/Union.md): select from many different types
274+
- [Value](../api/Value.md): maps with any keys - variably sized
275+
- [Delete](../api/Delete.md): remove an entity
276+
277+
[Learn more](../api/schema.md)
278+
279+
### Nesting
280+
281+
Additionally, [Entities](../api/Entity.md) themselves can specify [nested](../guides/nested-response.md) [Entities](../api/Entity.md)
282+
by specifying a [static schema](../api/Entity.md#schema) member.
283+
284+
```typescript
285+
import { Entity } from '@rest-hooks/endpoint';
238286

239287
class Todo extends Entity {
240288
readonly id: number = 0;
241-
readonly userId: number = 0;
289+
readonly user: User = User.fromJS({});
242290
readonly title: string = '';
243291
readonly completed: boolean = false;
244292

245293
pk() {
246294
return `${this.id}`;
247295
}
296+
297+
// highlight-start
298+
static schema = {
299+
user: User,
300+
};
301+
// highlight-end
248302
}
249303

250-
const TodoDetail = new Endpoint(
251-
({ id }) ⇒ fetch(`https://jsonplaceholder.typicode.com/todos/${id}`),
252-
{ schema: Todo }
253-
);
304+
class User extends Entity {
305+
readonly id: number = 0;
306+
readonly username: string = '';
307+
308+
pk() {
309+
return `${this.id}`;
310+
}
311+
}
254312
```
255313

314+
[Learn more](../guides/nested-response.md)
315+
316+
### Data Representations
317+
318+
Additionally, any `newable` class that has a toJSON() method, can be [used as a schema](../guides/network-transform#deserializing-fields). This will simply construct the object during denormalization.
319+
This might be useful with representations like [bignumber](https://mikemcl.github.io/bignumber.js/)
320+
321+
```ts
322+
import { Entity } from '@rest-hooks/endpoint';
323+
324+
class Todo extends Entity {
325+
readonly id: number = 0;
326+
readonly user: User = User.fromJS({});
327+
readonly title: string = '';
328+
readonly completed: boolean = false;
329+
// highlight-next-line
330+
readonly createdAt: Date = new Date(0);
331+
332+
pk() {
333+
return `${this.id}`;
334+
}
335+
336+
static schema = {
337+
user: User,
338+
// highlight-next-line
339+
createdAt: Date,
340+
};
341+
}
342+
```
343+
344+
:::info
345+
346+
Due to the global referential equality guarantee - construction of members only occurs once
347+
per update.
256348

257-
<!-- nesting -->
349+
:::

docs/guides/auth.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
title: Authentication
33
---
4+
45
import Tabs from '@theme/Tabs';
56
import TabItem from '@theme/TabItem';
67

@@ -21,7 +22,7 @@ values={[
2122

2223
```typescript
2324
class AuthdResource extends Resource {
24-
static getFetchInit = (init: RequestInit) => ({
25+
static getFetchInit = (init: RequestInit): RequestInit => ({
2526
...init,
2627
credentials: 'same-origin',
2728
});
@@ -32,10 +33,12 @@ class AuthdResource extends Resource {
3233
<TabItem value="superagent">
3334

3435
```typescript
35-
import { Request } from 'rest-hooks';
36+
import { Resource } from 'rest-hooks';
37+
import type { SuperAgentRequest } from 'superagent';
3638

3739
class AuthdResource extends Resource {
38-
static fetchPlugin = (request: Request) => request.withCredentials();
40+
static fetchPlugin = (request: SuperAgentRequest) =>
41+
request.withCredentials();
3942
}
4043
```
4144

@@ -55,17 +58,16 @@ class AuthdResource extends Resource {
5558
static useFetchInit = (init: RequestInit) => {
5659
const accessToken = useAuthContext();
5760
return {
58-
...init,
61+
...init,
5962
headers: {
6063
...init.headers,
6164
'Access-Token': accessToken,
6265
},
63-
}
66+
};
6467
};
6568
}
6669
```
6770

68-
6971
## Code organization
7072

7173
If much of your `Resources` share a similar auth mechanism, you might

0 commit comments

Comments
 (0)