Skip to content

Commit

Permalink
feat: Add ctrl.setResponse, ctrl.setError (#2331)
Browse files Browse the repository at this point in the history
  • Loading branch information
ntucker committed Dec 17, 2022
1 parent b76c134 commit 46e3b63
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 72 deletions.
2 changes: 1 addition & 1 deletion docs/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ export default class StreamManager implements Manager {
try {
const msg = JSON.parse(event.data);
if (msg.type in this.endpoints)
controller.receive(this.endpoints[msg.type], ...msg.args, msg.data);
controller.setResponse(this.endpoints[msg.type], ...msg.args, msg.data);
} catch (e) {
console.error('Failed to handle message');
console.error(e);
Expand Down
20 changes: 14 additions & 6 deletions docs/core/api/Controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class Controller {
fetch(endpoint, ...args) => ReturnType<E>;
invalidate(endpoint, ...args) => Promise<void>;
resetEntireStore: () => Promise<void>;
receive(endpoint, ...args, response) => Promise<void>;
receiveError(endpoint, ...args, error) => Promise<void>;
setResponse(endpoint, ...args, response) => Promise<void>;
setError(endpoint, ...args, error) => Promise<void>;
resolve(endpoint, { args, response, fetchedAt, error }) => Promise<void>;
subscribe(endpoint, ...args) => Promise<void>;
unsubscribe(endpoint, ...args) => Promise<void>;
Expand Down Expand Up @@ -197,7 +197,11 @@ function UserName() {
}
```

## receive(endpoint, ...args, response) {#receive}
## receive() {#receive}

Another name for setResponse()

## setResponse(endpoint, ...args, response) {#setResponse}

Stores `response` in cache for given [Endpoint](/rest/api/Endpoint) and args.

Expand All @@ -212,7 +216,7 @@ useEffect(() => {
const websocket = new Websocket(url);

websocket.onmessage = event =>
ctrl.receive(EndpointLookup[event.endpoint], ...event.args, event.data);
ctrl.setResponse(EndpointLookup[event.endpoint], ...event.args, event.data);

return () => websocket.close();
});
Expand All @@ -221,15 +225,19 @@ useEffect(() => {
This shows a proof of concept in React; however a [Manager websockets implementation](./Manager.md#data-stream)
would be much more robust.

## receiveError(endpoint, ...args, error) {#receiveError}
## receiveError() {#receiveError}

Another name for setError()

## setError(endpoint, ...args, error) {#setError}

Stores the result of [Endpoint](/rest/api/Endpoint) and args as the error provided.

## resolve(endpoint, { args, response, fetchedAt, error }) {#resolve}

Resolves a specific fetch, storing the `response` in cache.

This is similar to receive, except it triggers resolution of an inflight fetch.
This is similar to setResponse, except it triggers resolution of an inflight fetch.
This means the corresponding optimistic update will no longer be applies.

This is used in [NetworkManager](./NetworkManager.md), and should be used when
Expand Down
4 changes: 2 additions & 2 deletions docs/core/api/Manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export default class StreamManager implements Manager {
try {
const msg = JSON.parse(event.data);
if (msg.type in this.endpoints)
controller.receive(this.endpoints[msg.type], ...msg.args, msg.data);
controller.setResponse(this.endpoints[msg.type], ...msg.args, msg.data);
} catch (e) {
console.error('Failed to handle message');
console.error(e);
Expand All @@ -228,5 +228,5 @@ export default class StreamManager implements Manager {
}
```

[Controller.receive()](./Controller.md#receive) updates the Rest Hooks store
[Controller.setResponse()](./Controller.md#setResponse) updates the Rest Hooks store
with `event.data`.
14 changes: 7 additions & 7 deletions docs/core/api/useCache.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ function useCache<

Excellent to use data in the normalized cache without fetching.

| Expiry Status | Returns | Conditions |
| ------------- | --------------- | ----------------------------------------------------------------------------------------------------- |
| Invalid | `undefined` | not in store, [deletion](/rest/api/createResource#delete), [invalidation](./Controller.md#invalidate), [invalidIfStale](../getting-started/expiry-policy.md#endpointinvalidifstale) |
| Stale | denormalized | (first-render, arg change) & [expiry &lt; now](../getting-started/expiry-policy.md) |
| Valid | denormalized | fetch completion |
| Ignore | `undefined` | `null` used as second argument |
| Expiry Status | Returns | Conditions |
| ------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Invalid | `undefined` | not in store, [deletion](/rest/api/createResource#delete), [invalidation](./Controller.md#invalidate), [invalidIfStale](../getting-started/expiry-policy.md#endpointinvalidifstale) |
| Stale | denormalized | (first-render, arg change) & [expiry &lt; now](../getting-started/expiry-policy.md) |
| Valid | denormalized | fetch completion |
| | `undefined` | `null` used as second argument |

## Example

Expand Down Expand Up @@ -139,7 +139,7 @@ const sortedUsers = new Query(
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
if (asc) return sorted;
return sorted.reverse();
}
},
);

function UsersPage() {
Expand Down
2 changes: 1 addition & 1 deletion docs/core/api/useController.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function useController(): Controller;

Provides access to [Controller](./Controller.md) which can be used for imperative control
over the cache. For instance [fetch](./Controller.md#fetch), [invalidate](./Controller.md#invalidate),
and [receive](./Controller.md#receive)
and [setResponse](./Controller.md#setResponse)

```tsx
import { useController } from '@rest-hooks/react';
Expand Down
15 changes: 7 additions & 8 deletions docs/core/api/useDLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@ function useDLE<

In case you cannot use [suspense](../getting-started/data-dependency.md#async-fallbacks), useDLE() is just like [useSuspense()](./useSuspense.md) but returns [D]ata [L]oading [E]rror values.

| Expiry Status | Fetch | Data | Loading | Error | Conditions |
| ------------- | --------------- | ------- | ----------------- | ----------------- | ----------------------------------------------------------------------------------------------------- |
| Invalid | yes<sup>1</sup> | `undefined` | true | false | not in store, [deletion](/rest/api/createResource#delete), [invalidation](./Controller.md#invalidate), [invalidIfStale](../getting-started/expiry-policy.md#endpointinvalidifstale) |
| Stale | yes<sup>1</sup> | denormalized | maybe<sup>2</sup> | false | (first-render, arg change) & [expiry &lt; now](../getting-started/expiry-policy.md) |
| Valid | no | denormalized | false | maybe<sup>2</sup> | fetch completion |
| Ignore | no | `undefined` | false | false | `null` used as second argument |
| Expiry Status | Fetch | Data | Loading | Error | Conditions |
| ------------- | --------------- | ------------ | ------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Invalid | yes<sup>1</sup> | `undefined` | true | false | not in store, [deletion](/rest/api/createResource#delete), [invalidation](./Controller.md#invalidate), [invalidIfStale](../getting-started/expiry-policy.md#endpointinvalidifstale) |
| Stale | yes<sup>1</sup> | denormalized | false | false | (first-render, arg change) & [expiry &lt; now](../getting-started/expiry-policy.md) |
| Valid | no | denormalized | false | maybe<sup>2</sup> | fetch completion |
| | no | `undefined` | false | false | `null` used as second argument |

:::note

1. Identical fetches are automatically deduplicated
2. [Hard errors](../getting-started/expiry-policy.md#error-policy) to be [caught](../getting-started/data-dependency#async-fallbacks) by [Error Boundaries](./AsyncBoundary.md)
:::
:::

<ConditionalDependencies hook="useDLE" />

Expand Down Expand Up @@ -113,4 +113,3 @@ render(<ProfileList />);
```

</HooksPlayground>

2 changes: 1 addition & 1 deletion docs/core/api/useFetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ This can be useful for ensuring resources early in a render tree before they are
| Invalid | yes<sup>1</sup> | Promise | not in store, [deletion](/rest/api/createResource#delete), [invalidation](./Controller.md#invalidate) |
| Stale | yes<sup>1</sup> | Promise | (first-render, arg change) & [expiry &lt; now](../getting-started/expiry-policy.md) |
| Valid | no | `undefined` | fetch completion |
| Ignore | no | `undefined` | `null` used as second argument |
| | no | `undefined` | `null` used as second argument |

:::note

Expand Down
12 changes: 6 additions & 6 deletions docs/core/api/useSuspense.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ Excellent for guaranteed data rendering.

Cache policy is [Stale-While-Revalidate](https://tools.ietf.org/html/rfc5861) by default but also [configurable](../getting-started/expiry-policy.md).

| Expiry Status | Fetch | Suspend | Error | Conditions |
| ------------- | --------------- | ----------------- | ----------------- | ----------------------------------------------------------------------------------------------------- |
| Invalid | yes<sup>1</sup> | yes | no | not in store, [deletion](/rest/api/createResource#delete), [invalidation](./Controller.md#invalidate), [invalidIfStale](../getting-started/expiry-policy.md#endpointinvalidifstale) |
| Stale | yes<sup>1</sup> | no | no | (first-render, arg change) & [expiry &lt; now](../getting-started/expiry-policy.md) |
| Valid | no | no | maybe<sup>2</sup> | fetch completion |
| Ignore | no | no | no | `null` used as second argument |
| Expiry Status | Fetch | Suspend | Error | Conditions |
| ------------- | --------------- | ------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Invalid | yes<sup>1</sup> | yes | no | not in store, [deletion](/rest/api/createResource#delete), [invalidation](./Controller.md#invalidate), [invalidIfStale](../getting-started/expiry-policy.md#endpointinvalidifstale) |
| Stale | yes<sup>1</sup> | no | no | (first-render, arg change) & [expiry &lt; now](../getting-started/expiry-policy.md) |
| Valid | no | no | maybe<sup>2</sup> | fetch completion |
| | no | no | no | `null` used as second argument |

:::note

Expand Down
2 changes: 1 addition & 1 deletion docs/core/guides/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ For example, when a useSuspense() hook is first mounted it might

- Start by dispatching a fetch action
- If no identical fetches are in-flight, the central store will then start the network call over HTTP
- When the network call resolves, a receive action is sent to the store's reducer, updating the state.
- When the network call resolves, a setResponse action is sent to the store's reducer, updating the state.
- The component is re-rendered with the updated state, resolving the suspense.

> [More about control flow](../api/Manager#control-flow)
Expand Down
8 changes: 4 additions & 4 deletions examples/benchmark/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function addReducerSuite(suite) {
controllerPop.dispatch = action => {
populatedState = reducerPop(state, action);
};
controllerPop.receive(getProject, data);
controllerPop.setResponse(getProject, data);
controllerPop.dispatch = action => {
reducerPop(populatedState, action);
};
Expand All @@ -38,14 +38,14 @@ export default function addReducerSuite(suite) {
return (
suite
.add('receiveLong', () => {
return controller.receive(getProject, data);
return controller.setResponse(getProject, data);
})
.add('receiveLongWithMerge', () => {
return controllerPop.receive(getProject, data);
return controllerPop.setResponse(getProject, data);
})
// biggest performance bump is not spreading in merge
.add('receiveLongWithSimpleMerge', () => {
return controllerPop.receive(getProjectSimple, data);
return controllerPop.setResponse(getProjectSimple, data);
})
);
}
42 changes: 38 additions & 4 deletions packages/core/src/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ export default class Controller<

/**
* Stores response in cache for given Endpoint and args.
* @see https://resthooks.io/docs/api/Controller#receive
* @see https://resthooks.io/docs/api/Controller#set
*/
receive = <
setResponse = <
E extends EndpointInterface & {
update?: EndpointUpdateFunction<E>;
},
Expand All @@ -156,11 +156,28 @@ export default class Controller<
return this.dispatch(action);
};

// TODO: deprecate
/**
* Another name for setResponse
* @see https://resthooks.io/docs/api/Controller#setResponse
*/
/* istanbul ignore next */ receive = <
E extends EndpointInterface & {
update?: EndpointUpdateFunction<E>;
},
>(
endpoint: E,
...rest: readonly [...Parameters<E>, any]
): Promise<void> => {
/* istanbul ignore next */
return this.setResponse(endpoint, ...rest);
};

/**
* Stores the result of Endpoint and args as the error provided.
* @see https://resthooks.io/docs/api/Controller#receiveError
* @see https://resthooks.io/docs/api/Controller#setError
*/
receiveError = <
setError = <
E extends EndpointInterface & {
update?: EndpointUpdateFunction<E>;
},
Expand All @@ -177,6 +194,23 @@ export default class Controller<
return this.dispatch(action);
};

// TODO: deprecate
/**
* Another name for setError
* @see https://resthooks.io/docs/api/Controller#setError
*/
/* istanbul ignore next */ receiveError = <
E extends EndpointInterface & {
update?: EndpointUpdateFunction<E>;
},
>(
endpoint: E,
...rest: readonly [...Parameters<E>, Error]
): Promise<void> => {
/* istanbul ignore next */
return this.setError(endpoint, ...rest);
};

/**
* Resolves an inflight fetch. `fetchedAt` should `fetch`'s `createdAt`
* @see https://resthooks.io/docs/api/Controller#resolve
Expand Down
6 changes: 5 additions & 1 deletion packages/react/src/components/__tests__/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,11 @@ describe('<CacheProvider />', () => {
unmount();
expect(debugspy).not.toHaveBeenCalled();
await act(() =>
injector.controller.receive(endpoint, { id: 5 }, { id: 5, title: 'hi' }),
injector.controller.setResponse(
endpoint,
{ id: 5 },
{ id: 5, title: 'hi' },
),
);
expect(debugspy).toHaveBeenCalled();
expect(debugspy.mock.calls[0]).toMatchInlineSnapshot(`
Expand Down

0 comments on commit 46e3b63

Please sign in to comment.