Skip to content

Commit

Permalink
feat: Add configuration to getDefaultManagers()
Browse files Browse the repository at this point in the history
  • Loading branch information
ntucker committed Jul 23, 2024
1 parent e4751d9 commit 3a3a867
Show file tree
Hide file tree
Showing 27 changed files with 450 additions and 137 deletions.
6 changes: 6 additions & 0 deletions .changeset/pink-dodos-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@data-client/react': patch
'@data-client/core': patch
---

Add jsdocs to IdlingNetworkManager
30 changes: 30 additions & 0 deletions .changeset/witty-papayas-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
'@data-client/react': patch
---

Add configuration to getDefaultManagers()

```ts
// completely remove DevToolsManager
const managers = getDefaultManagers({ devToolsManager: null });
```

```ts
// easier configuration
const managers = getDefaultManagers({
devToolsManager: {
// double latency to help with high frequency updates
latency: 1000,
// skip websocket updates as these are too spammy
predicate: (state, action) =>
action.type !== actionTypes.SET_TYPE || action.schema !== Ticker,
}
});
```

```ts
// passing instance allows us to use custom classes as well
const managers = getDefaultManagers({
networkManager: new CustomNetworkManager(),
});
```
2 changes: 1 addition & 1 deletion docs/core/api/DataProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ be useful for testing, or rehydrating the cache state when using server side ren

List of [Manager](./Manager.md#provided-managers)s use. This is the main extensibility point of the provider.

`getDefaultManagers()` can be used to extend the default managers.
[getDefaultManagers()](./getDefaultManagers.md) can be used to extend the default managers.

Default Production:

Expand Down
19 changes: 5 additions & 14 deletions docs/core/api/DevToolsManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ browser to get started.
[Arguments](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md)
to send to redux devtools.
For example, we can enable the `trace` option to help track down where actions are dispatched from.
For example, we can enable the [trace](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#trace) option to help track down where actions are dispatched from.
```tsx title="index.tsx"
import {
Expand All @@ -37,19 +37,10 @@ import {
} from '@data-client/react';
import ReactDOM from 'react-dom';

const managers =
process.env.NODE_ENV !== 'production'
? [
// highlight-start
new DevToolsManager({
trace: true,
}),
// highlight-end
...getDefaultManagers().filter(
manager => manager.constructor.name !== 'DevToolsManager',
),
]
: getDefaultManagers();
const managers = getDefaultManagers({
// highlight-next-line
devToolsManager: { trace: true },
});

ReactDOM.createRoot(document.body).render(
<DataProvider managers={managers}>
Expand Down
6 changes: 3 additions & 3 deletions docs/core/api/Manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import useBaseUrl from '@docusaurus/useBaseUrl';

# Manager

Managers are singletons that orchestrate the complex asynchronous behavior of `Reactive Data Client`.
Several managers are provided by `Reactive Data Client` and used by default; however there is nothing
`Managers` are singletons that orchestrate the complex asynchronous behavior of <abbr title="Reactive Data Client">Data Client</abbr>.
Several managers are provided by <abbr title="Reactive Data Client">Data Client</abbr> and used by default; however there is nothing
stopping other compatible managers to be built that expand the functionality. We encourage
PRs or complimentary libraries!

While managers often have complex internal state and methods - the exposed interface is quite simple.
Because of this, it is encouraged to keep any supporting state or methods marked at protected by
typescript. Managers have three exposed pieces - the constructor to build initial state and
typescript. `Managers` have three exposed pieces - the constructor to build initial state and
take any parameters; a simple cleanup() method to tear down any dangling pieces like setIntervals()
or unresolved Promises; and finally getMiddleware() - providing the mechanism to hook into
the flux data flow.
Expand Down
2 changes: 1 addition & 1 deletion docs/core/api/NetworkManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ it is able to dedupe identical requests if they are made using the throttle flag

## Members

### constructor(dataExpiryLength = 60000, errorExpiryLength = 1000) {#constructor}
### constructor({ dataExpiryLength = 60000, errorExpiryLength = 1000 }) {#constructor}

Arguments represent the default time (in miliseconds) before a resource is considered 'stale'.

Expand Down
128 changes: 128 additions & 0 deletions docs/core/api/getDefaultManagers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
title: getDefaultManagers() - Configuring managers for DataProvider
sidebar_label: getDefaultManagers
---

import StackBlitz from '@site/src/components/StackBlitz';

# getDefaultManagers()

`getDefaultManagers` returns an Array of [Managers](./Manager.md) to be sent to [&lt;DataProvider />](./DataProvider.md).

This makes it simple to configure and add custom [Managers](./Manager.md), while remaining robust against
any potential changes to the default managers.

Currently returns \[[DevToolsManager](./DevToolsManager.md)\*, [NetworkManager](./NetworkManager.md), [SubscriptionManager](./SubscriptionManager.md)\].

\*(`DevToolsManager` is excluded in production builds.)

## Usage

```tsx
import {
DevToolsManager,
DataProvider,
getDefaultManagers,
} from '@data-client/react';
import ReactDOM from 'react-dom';

// highlight-start
const managers = getDefaultManagers({
// set fallback expiry time to an hour
networkManager: { dataExpiryLength: 1000 * 60 * 60 },
});
// highlight-end

ReactDOM.createRoot(document.body).render(
<DataProvider managers={managers}>
<App />
</DataProvider>,
);
```

See [DataProvider](./DataProvider.md) for details on usage in different environments.

## Arguments

Each argument represents a configuration of the manager. It can be of three possible types:

- Any plain object is used as options to be sent to the manager's constructor.
- An instance of the manager to be used directly.
- `null`. When sent will exclude the manager.

```ts
getDefaultManagers({
devToolsManager: { trace: true },
networkManager: new NetworkManager({ errorExpiryLength: 1 }),
subscriptionManager: null,
});
```

### networkManager

:::note

`null` is not allowed here since NetworkManager is required

:::

`dataExpiryLength` is used as a fallback when an Endpoint does not have [dataExpiryLength](https://dataclient.io/docs/concepts/expiry-policy#endpointdataexpirylength) defined.

`errorExpiryLength` is used as a fallback when an Endpoint does not have [errorExpiryLength](https://dataclient.io/docs/concepts/expiry-policy#endpointerrorexpirylength) defined.

### devToolsManager

[Arguments](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md)
to send to redux devtools.

### subscriptionManager

A class that implements `SubscriptionConstructable` like [PollingSubscription](./PollingSubscription.md)

## Examples

### Tracing actions

For example, we can enable the [trace](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#trace) option to help track down where actions are dispatched from. This has a large performance impact, so it is normally disabled.

```ts
const managers = getDefaultManagers({
// highlight-next-line
devToolsManager: { trace: true },
});
```

### Manager inheritance

Sending manager instances allows us to customize managers using inheritance.

```ts
import { IdlingNetworkManager } from '@data-client/react';

const managers = getDefaultManagers({
networkManager: new IdlingNetworkManager(),
});
```

`IdlingNetworkManager` can prevent stuttering by delaying [sideEffect](/rest/api/Endpoint#sideeffect)-free (read-only/GET) fetches
until animations are complete. This works in web using [requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback), and react native using InteractionManager.runAfterInteractions.

### Disabling

Using `null` will remove managers completely. [NetworkManager](./NetworkManager.md) cannot be removed this way.

```ts
const managers = getDefaultManagers({
devToolsManager: null,
subscriptionManager: null,
});
```

Here we disable every manager except [NetworkManager](./NetworkManager.md).

### Coin App

New prices are streamed in many times a second; to reduce devtool spam, we set it
to ignore [SET](./Controller.md#set) actions for `Ticker`.

<StackBlitz app="coin-app" file="src/index.tsx,src/resources/StreamManager.ts,src/getManagers.ts" height="600" />
2 changes: 1 addition & 1 deletion docs/core/concepts/managers.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,4 @@ with `event.data`.

### Coin App

<StackBlitz app="coin-app" file="src/index.tsx,src/resources/Ticker.ts,src/pages/AssetDetail/AssetPrice.tsx,src/resources/StreamManager.ts" height="600" />
<StackBlitz app="coin-app" file="src/getManagers.tsx,src/resources/Ticker.ts,src/pages/AssetDetail/AssetPrice.tsx,src/resources/StreamManager.ts" height="600" />
17 changes: 4 additions & 13 deletions docs/core/getting-started/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,10 @@ import {
} from '@data-client/react';
import ReactDOM from 'react-dom';

const managers =
process.env.NODE_ENV !== 'production'
? [
// highlight-start
new DevToolsManager({
trace: true,
}),
// highlight-end
...getDefaultManagers().filter(
manager => manager.constructor.name !== 'DevToolsManager',
),
]
: getDefaultManagers();
const managers = getDefaultManagers({
// highlight-next-line
devToolsManager: { trace: true },
});

ReactDOM.createRoot(document.body).render(
<DataProvider managers={managers}>
Expand Down
21 changes: 21 additions & 0 deletions examples/coin-app/src/getManagers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getDefaultManagers, actionTypes } from '@data-client/react';
import StreamManager from 'resources/StreamManager';
import { Ticker } from 'resources/Ticker';

export default function getManagers() {
return [
new StreamManager(
() => new WebSocket('wss://ws-feed.exchange.coinbase.com'),
{ ticker: Ticker },
),
...getDefaultManagers({
devToolsManager: {
// double latency to help with high frequency updates
latency: 1000,
// skip websocket updates as these are too spammy
predicate: (state, action) =>
action.type !== actionTypes.SET_TYPE || action.schema !== Ticker,
},
}),
];
}
48 changes: 3 additions & 45 deletions examples/coin-app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,8 @@ import {
JSONSpout,
appSpout,
} from '@anansi/core';
import {
useController,
AsyncBoundary,
getDefaultManagers,
DevToolsManager,
NetworkManager,
actionTypes,
} from '@data-client/react';
import StreamManager from 'resources/StreamManager';
import { Ticker } from 'resources/Ticker';
import { useController, AsyncBoundary } from '@data-client/react';
import getManagers from 'getManagers';

import App from './App';
import { createRouter } from './routing';
Expand All @@ -28,17 +20,7 @@ const app = (

const spouts = JSONSpout()(
documentSpout({ title: 'Coin App' })(
dataClientSpout({
getManagers: () => {
return [
new StreamManager(
() => new WebSocket('wss://ws-feed.exchange.coinbase.com'),
{ ticker: Ticker },
),
...getManagers(),
];
},
})(
dataClientSpout({ getManagers })(
routerSpout({
useResolveWith: useController,
createRouter,
Expand All @@ -47,28 +29,4 @@ const spouts = JSONSpout()(
),
);

function getManagers() {
const managers = getDefaultManagers().filter(
manager => manager.constructor.name !== 'DevToolsManager',
);
if (process.env.NODE_ENV !== 'production') {
const networkManager: NetworkManager | undefined = managers.find(
manager => manager instanceof NetworkManager,
) as any;
managers.unshift(
new DevToolsManager(
{
// double latency to help with high frequency updates
latency: 1000,
// skip websocket updates as these are too spammy
predicate: (state, action) =>
action.type !== actionTypes.SET_TYPE || action.schema !== Ticker,
},
networkManager && (action => networkManager.skipLogging(action)),
),
);
}
return managers;
}

export default floodSpouts(spouts);
5 changes: 3 additions & 2 deletions packages/core/src/manager/SubscriptionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ export interface SubscriptionConstructable {
*
* @see https://dataclient.io/docs/api/SubscriptionManager
*/
export default class SubscriptionManager<S extends SubscriptionConstructable>
implements Manager<Actions>
export default class SubscriptionManager<
S extends SubscriptionConstructable = SubscriptionConstructable,
> implements Manager<Actions>
{
protected subscriptions: {
[key: string]: InstanceType<S>;
Expand Down
Loading

0 comments on commit 3a3a867

Please sign in to comment.