GitPedia

Data client

Async State Management without the Management. REST, GraphQL, SSE, Websockets

From reactive·Updated June 17, 2026·View on GitHub·

The scalable way to build applications with [dynamic data](https://dataclient.io/docs/getting-started/mutations). The project is written primarily in TypeScript, distributed under the Apache License 2.0 license, first published in 2019. It has gained significant community traction with 2,031 stars and 98 forks on GitHub. Key topics include: expo, fetch, hooks, normalized, normalizr.

Latest release: @data-client/vue@0.18.1
May 13, 2026View Changelog →
<div align="center"> <a href="https://dataclient.io" target="_blank" rel="noopener"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://github.com/reactive/data-client/raw/master/website/static/img/client_logo_and_text-border--dark.svg?sanitize=true"> <img alt="Reactive Data Client" src="https://github.com/reactive/data-client/raw/master/website/static/img/client_logo_and_text-border--light.svg?sanitize=true"> </picture> </a>

The scalable way to build applications with dynamic data.

Declarative resouce definitons for REST, GraphQL, Websockets+SSE and more
<br/>Performant rendering in React, NextJS, React Native, Expo, Vue

Schema driven. Zero updater functions.

CircleCI
Coverage Status
Percentage of issues still open
bundle size
npm version
PRs Welcome
Agent Skills
Chat

📖Read The Docs  |  🏁Getting Started  |  🤖Agent Skills<br/>🎮 Demos:  
Todo  | 
Github Social  | 
NextJS SSR  | 
Websockets+SSR

</div>

Installation

bash
npm install --save @data-client/react @data-client/rest @data-client/test

For more details, see the Getting Started docs page.

Skills

bash
npx skills add reactive/data-client

Then run skill "data-client-setup"

Usage

Simple TypeScript definition

typescript
class User extends Entity { id = ''; username = ''; } class Article extends Entity { id = ''; title = ''; body = ''; author = User.fromJS(); createdAt = Temporal.Instant.fromEpochMilliseconds(0); static schema = { author: User, createdAt: Temporal.Instant.from, }; }

Create collection of API Endpoints

typescript
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', });

One line data binding

tsx
const article = useSuspense(ArticleResource.get, { id }); return ( <article> <h2> {article.title} by {article.author.username} </h2> <p>{article.body}</p> </article> );

Reactive Mutations

tsx
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> </> );

Subscriptions

tsx
const price = useLive(PriceResource.get, { symbol }); return price.value;

Type-safe Imperative Actions

tsx
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);

Programmatic queries

typescript
const queryTotalVotes = new Query( new Collection([BlogPost]), posts => posts.reduce((total, post) => total + post.votes, 0), ); const totalVotes = useQuery(queryTotalVotes); const totalVotesForUser = useQuery(queryTotalVotes, { userId });
typescript
const groupTodoByUser = new Query( TodoResource.getList.schema, todos => Object.groupBy(todos, todo => todo.userId), ); const todosByUser = useQuery(groupTodoByUser);

Powerful Middlewares

ts
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() {} }
ts
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(); } }

Integrated data mocking

tsx
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> );

...all typed ...fast ...and consistent

For the small price of 9kb gziped.    🏁Get started now

Features

Examples

  • Todo: GitHub | Sandbox | Edit on CodeSandbox
  • Github: GitHub | Sandbox
  • NextJS: GitHub | Sandbox | Edit on CodeSandbox
  • Websockets: GitHub | Sandbox | Website

API

Reactive Applications

  • Rendering: useSuspense(), useLive(), useCache(), useDLE(), useQuery(), useLoading(), useDebounce(), useCancelling()

  • Event handling: useController() returns Controller

    <table> <thead> <tr> <th>Method</th> <th>Subject</th> </tr> </thead> <tbody> <tr> <th colSpan="2" align="center">Fetch</th> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#fetch">ctrl.fetch</a></td> <td>Endpoint + Args</td> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#fetchIfStale">ctrl.fetchIfStale</a></td> <td>Endpoint + Args</td> </tr> <tr> <th colSpan="2" align="center">Expiry</th> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#expireAll">ctrl.expireAll</a></td> <td>Endpoint</td> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#invalidate">ctrl.invalidate</a></td> <td>Endpoint + Args</td> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#invalidateAll">ctrl.invalidateAll</a></td> <td>Endpoint</td> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#resetEntireStore">ctrl.resetEntireStore</a></td> <td>Everything</td> </tr> <tr> <th colSpan="2" align="center">Set</th> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#set">ctrl.set</a></td> <td>Schema + Args</td> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#setResponse">ctrl.setResponse</a></td> <td>Endpoint + Args</td> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#setError">ctrl.setError</a></td> <td>Endpoint + Args</td> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#resolve">ctrl.resolve</a></td> <td>Endpoint + Args</td> </tr> <tr> <th colSpan="2" align="center">Subscription</th> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#subscribe">ctrl.subscribe</a></td> <td>Endpoint + Args</td> </tr> <tr> <td><a href="https://dataclient.io/docs/api/Controller#unsubscribe">ctrl.unsubscribe</a></td> <td>Endpoint + Args</td> </tr> </tbody> </table>
  • Components: <DataProvider/>, <AsyncBoundary/>, <ErrorBoundary/>, <MockResolver/>

  • Data Mocking: Fixture, Interceptor, renderDataHook()

  • Middleware: LogoutManager, NetworkManager, SubscriptionManager, PollingSubscription, DevToolsManager

Define Data

Networking definition

<table> <caption> <a href="https://dataclient.io/docs/concepts/normalization">Data model</a> </caption> <thead> <tr> <th>Data Type</th> <th>Mutable</th> <th>Schema</th> <th>Description</th> <th><a href="https://dataclient.io/rest/api/schema#queryable">Queryable</a></th> </tr> </thead> <tbody><tr> <td rowSpan="4"><a href="https://en.wikipedia.org/wiki/Object_(computer_science)">Object</a></td> <td align="center">✅</td> <td><a href="https://dataclient.io/rest/api/Entity">Entity</a>, <a href="https://dataclient.io/rest/api/EntityMixin">EntityMixin</a></td> <td>single <em>unique</em> object</td> <td align="center">✅</td> </tr> <tr> <td align="center">✅</td> <td><a href="https://dataclient.io/rest/api/Union">Union(Entity)</a></td> <td>polymorphic objects (<code>A | B</code>)</td> <td align="center">✅</td> </tr> <tr> <td align="center">🛑</td> <td><a href="https://dataclient.io/rest/api/Object">Object</a></td> <td>statically known keys</td> <td align="center">🛑</td> </tr> <tr> <td align="center"></td> <td><a href="https://dataclient.io/rest/api/Invalidate">Invalidate(Entity)</a></td> <td><a href="https://dataclient.io/docs/concepts/expiry-policy#invalidate-entity">delete an entity</a></td> <td align="center">🛑</td> </tr> <tr> <td rowSpan="3"><a href="https://en.wikipedia.org/wiki/List_(abstract_data_type)">List</a></td> <td align="center">✅</td> <td><a href="https://dataclient.io/rest/api/Collection">Collection(Array)</a></td> <td>growable lists</td> <td align="center">✅</td> </tr> <tr> <td align="center">🛑</td> <td><a href="https://dataclient.io/rest/api/Array">Array</a></td> <td>immutable lists</td> <td align="center">🛑</td> </tr> <tr> <td align="center"> </td> <td><a href="https://dataclient.io/rest/api/All">All</a></td> <td>list of all entities of a kind</td> <td align="center">✅</td> </tr> <tr> <td rowSpan="2"><a href="https://en.wikipedia.org/wiki/Associative_array">Map</a></td> <td align="center">✅</td> <td><a href="https://dataclient.io/rest/api/Collection">Collection(Values)</a></td> <td>growable maps</td> <td align="center">✅</td> </tr> <tr> <td align="center">🛑</td> <td><a href="https://dataclient.io/rest/api/Values">Values</a></td> <td>immutable maps</td> <td align="center">🛑</td> </tr> <tr> <td rowSpan="1"><a href="https://en.wikipedia.org/wiki/Scalar_(mathematics)">Scalar</a></td> <td align="center">✅</td> <td><a href="https://dataclient.io/rest/api/Scalar">Scalar</a></td> <td>lens-dependent entity fields</td> <td align="center">✅</td> </tr> <tr> <td rowSpan="2">any</td> <td align="center"></td> <td><a href="https://dataclient.io/rest/api/Query">Query(Queryable)</a></td> <td>memoized custom transforms</td> <td align="center">✅</td> </tr> <tr> <td align="center"></td> <td><a href="https://dataclient.io/rest/api/Lazy">Lazy(Schema)</a></td> <td>deferred denormalization</td> <td align="center">✅</td> </tr> </tbody></table>

Contributors

Showing top 12 contributors by commit count.

View all contributors on GitHub →

This article is auto-generated from reactive/data-client via the GitHub API.Last fetched: 6/23/2026