|
| 1 | +--- |
| 2 | +applyTo: '**/*.ts' |
| 3 | +--- |
| 4 | +# Guide: Using `@data-client/react` Managers for global side effects |
| 5 | + |
| 6 | +[Managers](https://dataclient.io/docs/api/Manager) are singletons that handle global side-effects. Kind of like useEffect() for the central data store. |
| 7 | +They interface with the store using [Controller](https://dataclient.io/docs/api/Controller), and [redux middleware](https://redux.js.org/tutorials/fundamentals/part-4-store#middleware) is run in response to [actions](https://dataclient.io/docs/api/Actions). |
| 8 | + |
| 9 | +## Dispatching actions |
| 10 | + |
| 11 | +[Controller](https://dataclient.io/docs/api/Controller) has dispatchers: |
| 12 | +ctrl.fetch(), ctrl.fetchIfStale(), ctrl.expireAll(), ctrl.invalidate(), ctrl.invalidateAll(), ctrl.setResponse(), ctrl.set(). |
| 13 | + |
| 14 | +```ts |
| 15 | +import type { Manager, Middleware } from '@data-client/core'; |
| 16 | +import CurrentTime from './CurrentTime'; |
| 17 | + |
| 18 | +export default class TimeManager implements Manager { |
| 19 | + protected declare intervalID?: ReturnType<typeof setInterval>; |
| 20 | + |
| 21 | + middleware: Middleware = controller => { |
| 22 | + this.intervalID = setInterval(() => { |
| 23 | + controller.set(CurrentTime, { id: 1 }, { id: 1, time: Date.now() }); |
| 24 | + }, 1000); |
| 25 | + |
| 26 | + return next => async action => next(action); |
| 27 | + }; |
| 28 | + |
| 29 | + cleanup() { |
| 30 | + clearInterval(this.intervalID); |
| 31 | + } |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +## Reading and Consuming Actions |
| 36 | + |
| 37 | +[Controller](https://dataclient.io/docs/api/Controller) has data accessors: |
| 38 | +controller.getResponse(), controller.getState(), controller.get(), controller.getError() |
| 39 | + |
| 40 | +```ts |
| 41 | +import type { Manager, Middleware } from '@data-client/react'; |
| 42 | +import { actionTypes } from '@data-client/react'; |
| 43 | + |
| 44 | +export default class LoggingManager implements Manager { |
| 45 | + middleware: Middleware = controller => next => async action => { |
| 46 | + switch (action.type) { |
| 47 | + case actionTypes.SET_RESPONSE: |
| 48 | + if (action.endpoint.sideEffect) { |
| 49 | + console.info( |
| 50 | + `${action.endpoint.name} ${JSON.stringify(action.response)}`, |
| 51 | + ); |
| 52 | + // wait for state update to be committed to React |
| 53 | + await next(action); |
| 54 | + // get the data from the store, which may be merged with existing state |
| 55 | + const { data } = controller.getResponse( |
| 56 | + action.endpoint, |
| 57 | + ...action.args, |
| 58 | + controller.getState(), |
| 59 | + ); |
| 60 | + console.info(`${action.endpoint.name} ${JSON.stringify(data)}`); |
| 61 | + return; |
| 62 | + } |
| 63 | + // actions must be explicitly passed to next middleware |
| 64 | + default: |
| 65 | + return next(action); |
| 66 | + } |
| 67 | + }; |
| 68 | + |
| 69 | + cleanup() {} |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +`actionTypes`: FETCH, SET, SET_RESPONSE, RESET, SUBSCRIBE, UNSUBSCRIBE, INVALIDATE, INVALIDATEALL, EXPIREALL |
| 74 | + |
| 75 | +[actions](https://dataclient.io/docs/api/Actions) docs details the action types and their payloads. |
| 76 | + |
| 77 | +## Consuming actions |
| 78 | + |
| 79 | +```ts |
| 80 | +import type { Manager, Middleware, EntityInterface } from '@data-client/react'; |
| 81 | +import { actionTypes } from '@data-client/react'; |
| 82 | +import isEntity from './isEntity'; |
| 83 | + |
| 84 | +export default class CustomSubsManager implements Manager { |
| 85 | + protected declare entities: Record<string, EntityInterface>; |
| 86 | + |
| 87 | + middleware: Middleware = controller => next => async action => { |
| 88 | + switch (action.type) { |
| 89 | + case actionTypes.SUBSCRIBE: |
| 90 | + case actionTypes.UNSUBSCRIBE: |
| 91 | + const { schema } = action.endpoint; |
| 92 | + // only process registered entities |
| 93 | + if (schema && isEntity(schema) && schema.key in this.entities) { |
| 94 | + if (action.type === actionTypes.SUBSCRIBE) { |
| 95 | + this.subscribe(schema.key, action.args[0]?.product_id); |
| 96 | + } else { |
| 97 | + this.unsubscribe(schema.key, action.args[0]?.product_id); |
| 98 | + } |
| 99 | + |
| 100 | + // consume subscription to prevent it from being processed by other managers |
| 101 | + return Promise.resolve(); |
| 102 | + } |
| 103 | + default: |
| 104 | + return next(action); |
| 105 | + } |
| 106 | + }; |
| 107 | + |
| 108 | + cleanup() {} |
| 109 | + |
| 110 | + subscribe(channel: string, product_id: string) {} |
| 111 | + unsubscribe(channel: string, product_id: string) {} |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +## Usage |
| 116 | + |
| 117 | +```tsx |
| 118 | +import { DataProvider, getDefaultManagers } from '@data-client/react'; |
| 119 | +import ReactDOM from 'react-dom'; |
| 120 | + |
| 121 | +const managers = [...getDefaultManagers(), new MyManager()]; |
| 122 | + |
| 123 | +ReactDOM.createRoot(document.body).render( |
| 124 | + <DataProvider managers={managers}> |
| 125 | + <App /> |
| 126 | + </DataProvider>, |
| 127 | +); |
| 128 | +``` |
0 commit comments