Async State Management without the Management. REST, GraphQL, SSE, Websockets
The scalable way to build applications with dynamic data.
Declarative resouce definitons for REST, GraphQL, Websockets+SSE and more
Performant rendering in React, NextJS, React Native, ExpoGo
Schema driven. Zero updater functions.
๐Read The Docs ย |ย ๐Getting Started
๐ฎ Demos: ย
Todo ย |ย
Github Social ย |ย
NextJS SSR ย |ย
Websockets+SSR
npm install --save @data-client/react @data-client/rest @data-client/test
For more details, see the Installation docs page.
class User extends Entity {
id = '';
username = '';
}
class Article extends Entity {
id = '';
title = '';
body = '';
author = User.fromJS();
createdAt = Temporal.Instant.fromEpochSeconds(0);
static schema = {
author: User,
createdAt: Temporal.Instant.from,
};
}
const UserResource = resource({
path: '/users/:id',
schema: User,
optimistic: true,
});
const ArticleResource = resource({
path: '/articles/:id',
schema: Article,
searchParams: {} as { author?: string },
optimistic: true,
paginationField: 'cursor',
});
const article = useSuspense(ArticleResource.get, { id });
return (
<article>
<h2>
{article.title} by {article.author.username}
</h2>
<p>{article.body}</p>
</article>
);
const ctrl = useController();
return (
<>
<CreateArticleForm
onSubmit={article =>
ctrl.fetch(ArticleResource.getList.push, { id }, article)
}
/>
<ProfileForm
onSubmit={user =>
ctrl.fetch(UserResource.update, { id: article.author.id }, user)
}
/>
<button onClick={() => ctrl.fetch(ArticleResource.delete, { id })}>
Delete
</button>
</>
);
const price = useLive(PriceResource.get, { symbol });
return price.value;
const ctrl = useController();
await ctrl.fetch(ArticleResource.update, { id }, articleData);
await ctrl.fetchIfStale(ArticleResource.get, { id });
ctrl.expireAll(ArticleResource.getList);
ctrl.invalidate(ArticleResource.get, { id });
ctrl.invalidateAll(ArticleResource.getList);
ctrl.setResponse(ArticleResource.get, { id }, articleData);
ctrl.set(Article, { id }, articleData);
const queryTotalVotes = new schema.Query(
new schema.Collection([BlogPost]),
posts => posts.reduce((total, post) => total + post.votes, 0),
);
const totalVotes = useQuery(queryTotalVotes);
const totalVotesForUser = useQuery(queryTotalVotes, { userId });
const groupTodoByUser = new schema.Query(
TodoResource.getList.schema,
todos => Object.groupBy(todos, todo => todo.userId),
);
const todosByUser = useQuery(groupTodoByUser);
class LoggingManager implements Manager {
middleware: Middleware = controller => next => async action => {
console.log('before', action, controller.getState());
await next(action);
console.log('after', action, controller.getState());
};
cleanup() {}
}
class TickerStream implements Manager {
middleware: Middleware = controller => {
this.handleMsg = msg => {
controller.set(Ticker, { id: msg.id }, msg);
};
return next => action => next(action);
};
init() {
this.websocket = new WebSocket('wss://ws-feed.myexchange.com');
this.websocket.onmessage = event => {
const msg = JSON.parse(event.data);
this.handleMsg(msg);
};
}
cleanup() {
this.websocket.close();
}
}
const fixtures = [
{
endpoint: ArticleResource.getList,
args: [{ maxResults: 10 }] as const,
response: [
{
id: '5',
title: 'first post',
body: 'have a merry christmas',
author: { id: '10', username: 'bob' },
createdAt: new Date(0).toISOString(),
},
{
id: '532',
title: 'second post',
body: 'never again',
author: { id: '10', username: 'bob' },
createdAt: new Date(0).toISOString(),
},
],
},
{
endpoint: ArticleResource.update,
response: ({ id }, body) => ({
...body,
id,
}),
},
];
const Story = () => (
<MockResolver fixtures={options[result]}>
<ArticleList maxResults={10} />
</MockResolver>
);
For the small price of 9kb gziped. ย ย ๐Get started now
Rendering: useSuspense(), useLive(), useCache(), useDLE(), useQuery(), useLoading(), useDebounce(), useCancelling()
Event handling: useController() returns Controller
Method | Subject |
---|---|
Fetch | |
ctrl.fetch | Endpoint + Args |
ctrl.fetchIfStale | Endpoint + Args |
Expiry | |
ctrl.expireAll | Endpoint |
ctrl.invalidate | Endpoint + Args |
ctrl.invalidateAll | Endpoint |
ctrl.resetEntireStore | Everything |
Set | |
ctrl.set | Schema + Args |
ctrl.setResponse | Endpoint + Args |
ctrl.setError | Endpoint + Args |
ctrl.resolve | Endpoint + Args |
Subscription | |
ctrl.subscribe | Endpoint + Args |
ctrl.unsubscribe | Endpoint + Args |
Components: <DataProvider/>, <AsyncBoundary/>, <ErrorBoundary/>, <MockResolver/>
Data Mocking: Fixture, Interceptor, renderDataHook()
Middleware: LogoutManager, NetworkManager, SubscriptionManager, PollingSubscription, DevToolsManager
Data Type | Mutable | Schema | Description | Queryable |
---|---|---|---|---|
Object | โ | Entity, EntityMixin | single unique object | โ |
โ | Union(Entity) | polymorphic objects (A | B ) |
โ | |
๐ | Object | statically known keys | ๐ | |
Invalidate(Entity) | delete an entity | ๐ | ||
List | โ | Collection(Array) | growable lists | โ |
๐ | Array | immutable lists | ๐ | |
โ | All | list of all entities of a kind | โ | |
Map | โ | Collection(Values) | growable maps | โ |
๐ | Values | immutable maps | ๐ | |
any | Query(Queryable) | memoized custom transforms | โ |