Skip to main content

<DataProvider />

Manages state, providing all context needed to use the hooks. Should be placed as high as possible in application tree as any usage of the hooks is only possible for components below the provider in the React tree.

index.tsx
import { DataProvider } from '@data-client/react';
import ReactDOM from 'react-dom';

ReactDOM.createRoot(document.body).render(
<DataProvider>
<App />
</DataProvider>,
);

Alternatively integrate state with redux

Props

interface ProviderProps {
children: ReactNode;
managers?: Manager[] | (() => Manager[]);
initialState?: State<unknown>;
Controller?: typeof Controller;
devButton?:
| 'bottom-right'
| 'bottom-left'
| 'top-right'
| 'top-left'
| null;
}

initialState: State<unknown>

export interface State<T> {
readonly entities: {
readonly [entityKey: string]: { readonly [pk: string]: T } | undefined;
};
readonly endpoints: {
readonly [key: string]: unknown | PK[] | PK | undefined;
};
readonly indexes: NormalizedIndex;
readonly meta: {
readonly [key: string]: {
readonly date: number;
readonly error?: ErrorTypes;
readonly expiresAt: number;
readonly prevExpiresAt?: number;
readonly invalidated?: boolean;
readonly errorPolicy?: 'hard' | 'soft' | undefined;
};
};
readonly entitiesMeta: {
readonly [entityKey: string]: {
readonly [pk: string]: {
readonly date: number;
readonly expiresAt: number;
readonly fetchedAt: number;
};
};
};
readonly optimistic: (SetResponseAction | OptimisticAction)[];
readonly lastReset: number;
}

Instead of starting with an empty cache, you can provide your own initial state. This can be useful for testing, or rehydrating the cache state when using server side rendering.

While React hydrates server-rendered HTML, hooks read from initialState rather than the live store, so a Suspense boundary that hydrates after a Manager has already updated the store still matches its HTML. Once hydrated, components render the live state. Anything missing from initialState is read from the live store, so it is fetched once like any other client render.

The Next.js App Router provider from @data-client/react/nextjs fills this in from the stream; see the SSR guide for its props.

managers?: Manager[] | (() => Manager[])

List of Managers to use, or a function that creates them. This is the main extensibility point of the provider.

getDefaultManagers() can be used to extend the default managers.

Both forms are resolved once when the provider mounts. Prefer the function: the same definition works with the Next.js provider, which only accepts a function, and each DataProvider on a page gets its own instances (managers keep a reference to the store they were attached to).

Arrays are transitional

Passing Manager[] keeps working for now but will be removed in a future release. Migrate to the function form when you can:

// Before:
const managers = [...getDefaultManagers(), new MyManager()];
// After:
const managers = () => [...getDefaultManagers(), new MyManager()];

Default Production:

[new NetworkManager(), new SubscriptionManager(PollingSubscription)];

Default Development:

[
new DevToolsManager(),
new NetworkManager(),
new SubscriptionManager(PollingSubscription),
];

Controller: typeof Controller

This allows you to extend Controller to provide additional functionality. This might be useful if you have additional actions you want to dispatch to custom Managers

class MyController extends Controller {
doSomething = () => {
console.log('hi');
};
}

const RealApp = (
<DataProvider Controller={MyController}>
<App />
</DataProvider>
);

devButton

In development, a small button will appear that gives easy access to browser devtools if installed. This option configures where it shows up, or if null will disable it altogether.

'bottom-right' | 'bottom-left' | 'top-right'| 'top-left' | null = 'bottom-right'

Disable button
<DataProvider devButton={null}>
<App/>
</DataProvider>
Place in top right corner
<DataProvider devButton="top-right">
<App/>
</DataProvider>