|
| 1 | +--- |
| 2 | +applyTo: '**/*.tsx' |
| 3 | +--- |
| 4 | +## Rendering |
| 5 | + |
| 6 | +```ts |
| 7 | +// GET https://jsonplaceholder.typicode.com/todos/5 |
| 8 | +const todo = useSuspense(TodoResource.get, { id: 5 }); |
| 9 | +// GET https://jsonplaceholder.typicode.com/todos |
| 10 | +const todoList = useSuspense(TodoResource.getList); |
| 11 | +// GET https://jsonplaceholder.typicode.com/todos?userId=1 |
| 12 | +const todoListByUser = useSuspense(TodoResource.getList, { userId: 1 }); |
| 13 | +// subscriptions |
| 14 | +const todo = useLive(TodoResource.get, { id: 5 }); |
| 15 | +// without fetch |
| 16 | +const todo = useCache(TodoResource.get, { id: 5 }); |
| 17 | +const todo = useQuery(Todo, { id: 5 }); |
| 18 | +``` |
| 19 | + |
| 20 | +## Mutations |
| 21 | + |
| 22 | +```ts |
| 23 | +const ctrl = useController(); |
| 24 | +// PUT https://jsonplaceholder.typicode.com/todos/5 |
| 25 | +const updateTodo = todo => ctrl.fetch(TodoResource.update, { id }, todo); |
| 26 | +// PATCH https://jsonplaceholder.typicode.com/todos/5 |
| 27 | +const partialUpdateTodo = todo => |
| 28 | + ctrl.fetch(TodoResource.partialUpdate, { id }, todo); |
| 29 | +// POST https://jsonplaceholder.typicode.com/todos |
| 30 | +const addTodoToBeginning = todo => |
| 31 | + ctrl.fetch(TodoResource.getList.unshift, todo); |
| 32 | +// POST https://jsonplaceholder.typicode.com/todos?userId=1 |
| 33 | +const addTodoToEnd = todo => ctrl.fetch(TodoResource.getList.push, { userId: 1 }, todo); |
| 34 | +// DELETE https://jsonplaceholder.typicode.com/todos/5 |
| 35 | +const deleteTodo = id => ctrl.fetch(TodoResource.delete, { id }); |
| 36 | +// GET https://jsonplaceholder.typicode.com/todos?userId=1&page=2 |
| 37 | +const getNextPage = (page) => ctrl.fetch(TodoResource.getList.getPage, { userId: 1, page }) |
| 38 | +``` |
| 39 | + |
| 40 | +## Helpful hooks |
| 41 | + |
| 42 | +```tsx |
| 43 | +const [handleSubmit, loading, error] = useLoading( |
| 44 | + async data => { |
| 45 | + const post = await ctrl.fetch(PostResource.getList.push, data); |
| 46 | + navigateToPost(post.id); |
| 47 | + }, |
| 48 | + [ctrl], |
| 49 | +); |
| 50 | +``` |
| 51 | + |
| 52 | +```tsx |
| 53 | +const [query, setQuery] = React.useState(''); |
| 54 | +const handleChange = e => setQuery(e.currentTarget.value); |
| 55 | +const [debouncedQuery, isPending] = useDebounce(query, 200); |
| 56 | + |
| 57 | +return ( |
| 58 | + <AsyncBoundary fallback={<Loading />}> |
| 59 | + <IssueList query={debouncedQuery} owner="facebook" repo="react" /> |
| 60 | + </AsyncBoundary> |
| 61 | +) |
| 62 | +``` |
| 63 | + |
| 64 | +## Components |
| 65 | + |
| 66 | +Prefer using `AsyncBoundary` for error handling and loading states. Its props are `fallback`, `errorComponent`, and `errorClassName` and `listen`. It can be used to wrap any component that fetches data. |
| 67 | + |
| 68 | +```tsx |
| 69 | +<AsyncBoundary listen={history.listen}> |
| 70 | + <TodoList /> |
| 71 | +</AsyncBoundary> |
| 72 | +``` |
| 73 | + |
| 74 | +## Type-safe imperative actions |
| 75 | + |
| 76 | +`Controller` is returned from `useController()`. It has: ctrl.fetch(), ctrl.fetchIfStale(), ctrl.expireAll(), ctrl.invalidate(), ctrl.invalidateAll(), ctrl.setResponse(), ctrl.set(). |
| 77 | + |
| 78 | +## Programmatic queries |
| 79 | + |
| 80 | +```ts |
| 81 | +const queryRemainingTodos = new schema.Query( |
| 82 | + TodoResource.getList.schema, |
| 83 | + entries => entries.filter(todo => !todo.completed).length, |
| 84 | +); |
| 85 | + |
| 86 | +const allRemainingTodos = useQuery(queryRemainingTodos); |
| 87 | +const firstUserRemainingTodos = useQuery(queryRemainingTodos, { userId: 1 }); |
| 88 | +``` |
| 89 | + |
| 90 | +```ts |
| 91 | +const groupTodoByUser = new schema.Query( |
| 92 | + TodoResource.getList.schema, |
| 93 | + todos => Object.groupBy(todos, todo => todo.userId), |
| 94 | +); |
| 95 | +const todosByUser = useQuery(groupTodoByUser); |
| 96 | +``` |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Managers |
| 101 | + |
| 102 | +Customer managers allow for global side effect handling. They interface with the store using `Controller`, and middleware is run in response to actions. |
| 103 | + |
| 104 | +```ts |
| 105 | +import type { Manager, Middleware, EntityInterface } from '@data-client/react'; |
| 106 | +import { actionTypes } from '@data-client/react'; |
| 107 | +import isEntity from './isEntity'; |
| 108 | + |
| 109 | +export default class CustomSubsManager implements Manager { |
| 110 | + protected declare entities: Record<string, EntityInterface>; |
| 111 | + |
| 112 | + middleware: Middleware = controller => next => async action => { |
| 113 | + switch (action.type) { |
| 114 | + case actionTypes.SUBSCRIBE: |
| 115 | + case actionTypes.UNSUBSCRIBE: |
| 116 | + const { schema } = action.endpoint; |
| 117 | + // only process registered entities |
| 118 | + if (schema && isEntity(schema) && schema.key in this.entities) { |
| 119 | + if (action.type === actionTypes.SUBSCRIBE) { |
| 120 | + this.subscribe(schema.key, action.args[0]?.product_id); |
| 121 | + } else { |
| 122 | + this.unsubscribe(schema.key, action.args[0]?.product_id); |
| 123 | + } |
| 124 | + |
| 125 | + // consume subscription if we use it |
| 126 | + return Promise.resolve(); |
| 127 | + } |
| 128 | + default: |
| 129 | + return next(action); |
| 130 | + } |
| 131 | + }; |
| 132 | + |
| 133 | + cleanup() {} |
| 134 | + |
| 135 | + subscribe(channel: string, product_id: string) {} |
| 136 | + unsubscribe(channel: string, product_id: string) {} |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +## Best Practices & Notes |
| 141 | + |
| 142 | +- `useDebounce(query, timeout);` when rendering async data based on user field inputs |
| 143 | +- `[handleSubmit, loading, error] = useLoading()` when tracking mutation loads like submitting an async form |
| 144 | +- Prefer smaller React components that do one thing. |
| 145 | + |
| 146 | +# Official Documentation Links |
| 147 | + |
| 148 | +- [Rendering](https://dataclient.io/docs/getting-started/data-dependency) |
| 149 | +- [Mutations](https://dataclient.io/docs/getting-started/mutations) |
| 150 | +- [Managers](https://dataclient.io/docs/concepts/managers) |
| 151 | +- [useSuspense](https://dataclient.io/docs/api/useSuspense) |
| 152 | +- [Controller](https://dataclient.io/docs/api/Controller) |
| 153 | + |
| 154 | +**ALWAYS follow these patterns and refer to the official docs for edge cases. Prioritize code generation that is idiomatic, type-safe, and leverages automatic normalization/caching via schema definitions.** |
0 commit comments