@@ -154,8 +154,8 @@ prevents 'data tearing' jank and improves performance.
154
154
import { Entity } from ' @data-client/rest' ;
155
155
156
156
export class Todo extends Entity {
157
- userId = 0 ;
158
157
id = 0 ;
158
+ userId = 0 ;
159
159
title = ' ' ;
160
160
completed = false ;
161
161
}
@@ -166,7 +166,6 @@ import { GQLEntity } from '@data-client/graphql';
166
166
167
167
export class Todo extends GQLEntity {
168
168
userId = 0 ;
169
- id = 0 ;
170
169
title = ' ' ;
171
170
completed = false ;
172
171
}
@@ -183,26 +182,28 @@ data integrity as well as TypeScript definitions.
183
182
184
183
<ProtocolTabs >
185
184
186
- ``` ts {6,13 }
185
+ ``` ts {6}
187
186
import { RestEndpoint } from ' @data-client/rest' ;
188
187
189
- export const getTodo = new RestEndpoint ({
188
+ const get = new RestEndpoint ({
190
189
urlPrefix: ' https://jsonplaceholder.typicode.com' ,
191
190
path: ' /todos/:id' ,
192
191
schema: Todo ,
193
192
});
194
193
195
- export const updateTodo = getTodo .extend ({
194
+ const update = getTodo .extend ({
196
195
method: ' PUT' ,
197
196
});
197
+
198
+ export const TodoResource = { get , update };
198
199
```
199
200
200
201
``` ts {14,25}
201
202
import { GQLEndpoint } from ' @data-client/graphql' ;
202
203
203
204
const gql = new GQLEndpoint (' /' );
204
205
205
- export const getTodo = gql .query (
206
+ const get = gql .query (
206
207
` query GetTodo($id: ID!) {
207
208
todo(id: $id) {
208
209
id
@@ -214,7 +215,7 @@ export const getTodo = gql.query(
214
215
{ todo: Todo },
215
216
);
216
217
217
- export const updateTodo = gql .mutation (
218
+ const update = gql .mutation (
218
219
` mutation UpdateTodo($todo: Todo!) {
219
220
updateTodo(todo: $todo) {
220
221
id
@@ -224,6 +225,8 @@ export const updateTodo = gql.mutation(
224
225
} ` ,
225
226
{ updateTodo: Todo },
226
227
);
228
+
229
+ export const TodoResource = { get , update };
227
230
```
228
231
229
232
</ProtocolTabs >
@@ -237,17 +240,32 @@ Just like `setState()`, we must make React aware of the any mutations so it can
237
240
238
241
We can [ useController] ( ./api/useController.md ) to access it in React components.
239
242
243
+ <ProtocolTabs >
244
+
245
+ ``` tsx
246
+ import { useController } from ' @data-client/react' ;
247
+
248
+ function ArticleEdit() {
249
+ const ctrl = useController ();
250
+ // highlight-next-line
251
+ const handleSubmit = data => ctrl .fetch (TodoResource .update , { id }, data );
252
+ return <ArticleForm onSubmit = { handleSubmit } />;
253
+ }
254
+ ```
255
+
240
256
``` tsx
241
257
import { useController } from ' @data-client/react' ;
242
258
243
259
function ArticleEdit() {
244
260
const ctrl = useController ();
245
261
// highlight-next-line
246
- const handleSubmit = data => ctrl .fetch (updateTodo , { id }, data );
262
+ const handleSubmit = data => ctrl .fetch (TodoResource . update , { id , ... data } );
247
263
return <ArticleForm onSubmit = { handleSubmit } />;
248
264
}
249
265
```
250
266
267
+ </ProtocolTabs >
268
+
251
269
<details >
252
270
<summary ><b >Tracking imperative loading/error state</b ></summary >
253
271
@@ -260,45 +278,36 @@ function ArticleEdit() {
260
278
const ctrl = useController ();
261
279
// highlight-next-line
262
280
const [handleSubmit, loading, error] = useLoading (
263
- data => ctrl .fetch (updateTodo , { id }, data ),
281
+ data => ctrl .fetch (TodoResource . update , { id }, data ),
264
282
[ctrl ],
265
283
);
266
284
return <ArticleForm onSubmit = { handleSubmit } loading = { loading } />;
267
285
}
268
286
```
269
287
270
- React 18 version with [ useTransition] ( https://react.dev/reference/react/useTransition )
271
-
272
- ``` tsx
273
- import { useTransition } from ' react' ;
274
- import { useController } from ' @data-client/react' ;
275
-
276
- function ArticleEdit() {
277
- const ctrl = useController ();
278
- const [loading, startTransition] = useTransition ();
279
- const handleSubmit = data =>
280
- startTransition (() => ctrl .fetch (updateTodo , { id }, data ));
281
- return <ArticleForm onSubmit = { handleSubmit } loading = { loading } />;
282
- }
283
- ```
284
-
285
288
</details >
286
289
287
290
### More data modeling
288
291
289
- What if our entity is not the top level item? Here we define the ` todoList `
290
- endpoint with ` [ Todo]` as its schema. [ Schemas] ( ./concepts/normalization.md#schema ) tell Reactive Data Client _ where_ to find
292
+ What if our entity is not the top level item? Here we define the ` getList `
293
+ endpoint with [ new schema.Collection( [ Todo] ) ] ( /rest/api/Collection ) as its schema. [ Schemas] ( ./concepts/normalization.md#schema ) tell Reactive Data Client _ where_ to find
291
294
the Entities. By placing inside a list, Reactive Data Client knows to expect a response
292
295
where each item of the list is the entity specified.
293
296
294
297
``` typescript {6}
295
298
import { RestEndpoint , schema } from ' @data-client/rest' ;
296
299
297
- export const getTodoList = new RestEndpoint ({
300
+ // get and update definitions omitted
301
+
302
+ const getList = new RestEndpoint ({
298
303
urlPrefix: ' https://jsonplaceholder.typicode.com' ,
299
304
path: ' /todos' ,
300
305
schema: new schema .Collection ([Todo ]),
306
+ searchParams: {} as { userId? : string | number } | undefined ,
307
+ paginationField: ' page' ,
301
308
});
309
+
310
+ export default TodoResource = { getList , get , update };
302
311
```
303
312
304
313
[ Schemas] ( ./concepts/normalization.md ) also automatically infer and enforce the response type, ensuring
@@ -308,7 +317,7 @@ the variable `todos` will be typed precisely.
308
317
import { useSuspense } from ' @data-client/react' ;
309
318
310
319
export default function TodoList() {
311
- const todos = useSuspense (getTodoList );
320
+ const todos = useSuspense (TodoResource . getList );
312
321
313
322
return (
314
323
<div >
@@ -320,12 +329,12 @@ export default function TodoList() {
320
329
}
321
330
```
322
331
323
- Now we've used our data model in three cases - ` getTodo ` , ` getTodoList ` and ` updateTodo ` . Data consistency
332
+ Now we've used our data model in three cases - ` TodoResource.get ` , ` TodoResource.getList ` and ` TodoResource.update ` . Data consistency
324
333
(as well as referential equality) will be guaranteed between the endpoints, even after mutations occur.
325
334
326
335
### Organizing Endpoints
327
336
328
- At this point we've defined ` todoDetail ` , ` todoList ` and ` todoUpdate ` . You might have noticed
337
+ At this point we've defined ` TodoResource.get ` , ` TodoResource.getList ` and ` TodoResource.update ` . You might have noticed
329
338
that these endpoint definitions share some logic and information. For this reason Reactive Data Client
330
339
encourages extracting shared logic among endpoints.
331
340
@@ -345,10 +354,12 @@ const TodoResource = resource({
345
354
urlPrefix: ' https://jsonplaceholder.typicode.com' ,
346
355
path: ' /todos/:id' ,
347
356
schema: Todo ,
357
+ searchParams: {} as { userId? : string | number } | undefined ,
358
+ paginationField: ' page' ,
348
359
});
349
360
```
350
361
351
- [ Introduction to Resource] ( /rest )
362
+ [ Introduction to Resource] ( ./getting-started/resource.md )
352
363
353
364
<details >
354
365
<summary ><b >Resource Endpoints</b ></summary >
@@ -361,12 +372,21 @@ const todo = useSuspense(TodoResource.get, { id: 5 });
361
372
// GET https://jsonplaceholder.typicode.com/todos
362
373
const todos = useSuspense (TodoResource .getList );
363
374
375
+ // GET https://jsonplaceholder.typicode.com/todos?userId=1
376
+ const todos = useSuspense (TodoResource .getList , { userId: 1 });
377
+
364
378
// mutate
365
379
const ctrl = useController ();
366
380
381
+ // GET https://jsonplaceholder.typicode.com/todos?userId=1
382
+ ctrl .fetch (TodoResource .getList .getPage , { userId: 1 , page: 2 });
383
+
367
384
// POST https://jsonplaceholder.typicode.com/todos
368
385
ctrl .fetch (TodoResource .getList .push , { title: ' my todo' });
369
386
387
+ // POST https://jsonplaceholder.typicode.com/todos?userId=1
388
+ ctrl .fetch (TodoResource .getList .push , { userId: 1 }, { title: ' my todo' });
389
+
370
390
// PUT https://jsonplaceholder.typicode.com/todos/5
371
391
ctrl .fetch (TodoResource .update , { id: 5 }, { title: ' my todo' });
372
392
@@ -395,7 +415,7 @@ we'll need to specify _how_.
395
415
value, as well as the fetch arguments, we return the _ expected_ fetch response.
396
416
397
417
``` typescript
398
- export const updateTodo = new RestEndpoint ({
418
+ const update = new RestEndpoint ({
399
419
urlPrefix: ' https://jsonplaceholder.typicode.com' ,
400
420
path: ' /todos/:id' ,
401
421
method: ' PUT' ,
@@ -428,32 +448,28 @@ which can be used to [initiate data updates](./concepts/managers.md#data-stream)
428
448
<summary ><b >StreamManager</b ></summary >
429
449
430
450
``` typescript
431
- import type { Manager , Middleware } from ' @data-client/core' ;
432
- import type { EndpointInterface } from ' @data-client/endpoint' ;
451
+ import type { Manager , Middleware , ActionTypes } from ' @data-client/react' ;
452
+ import { Controller , actionTypes } from ' @data-client/react' ;
453
+ import type { EntityInterface } from ' @data-client/rest' ;
433
454
434
455
export default class StreamManager implements Manager {
435
- protected declare middleware: Middleware ;
436
456
protected declare evtSource: WebSocket | EventSource ;
437
- protected declare endpoints : Record <string , EndpointInterface >;
457
+ protected declare entities : Record <string , typeof EntityInterface >;
438
458
439
459
constructor (
440
460
evtSource : WebSocket | EventSource ,
441
- endpoints : Record <string , EndpointInterface >,
461
+ entities : Record <string , EntityInterface >,
442
462
) {
443
463
this .evtSource = evtSource ;
444
- this .endpoints = endpoints ;
464
+ this .entities = entities ;
445
465
}
446
466
447
467
middleware: Middleware = controller => {
448
468
this .evtSource .onmessage = event => {
449
469
try {
450
470
const msg = JSON .parse (event .data );
451
471
if (msg .type in this .endpoints )
452
- controller .setResponse (
453
- this .endpoints [msg .type ],
454
- ... msg .args ,
455
- msg .data ,
456
- );
472
+ controller .set (this .entities [msg .type ], ... msg .args , msg .data );
457
473
} catch (e ) {
458
474
console .error (' Failed to handle message' );
459
475
console .error (e );
0 commit comments