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, Expo
Schema driven. Zero updater functions.
CircleCI Coverage Status Percentage of issues still open bundle size npm version PRs Welcome Codegen GPT Chat
📖Read The Docs | 🏁Getting Started | 🤖Codegen
🎮 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.
Simple TypeScript definition
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
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
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
- TS Strong Typescript inference
- 🛌 React Suspense support
- 🧵 React 18 Concurrent mode compatible
- 💦 Partial Hydration Server Side Rendering
- 🎣 Declarative API
- 📝 Composition over configuration
- 💰 Normalized caching
- 💥 Tiny bundle footprint
- 🛑 Automatic overfetching elimination
- ✨ Fast optimistic updates
- 🧘 Flexible to fit any API design (one size fits all)
- 🔧 Debugging and inspection via browser extension
- 🌳 Tree-shakable (only use what you need)
- 🔁 Subscriptions
- ♻️ Optional redux integration
- 📙 Storybook mocking
- 📱 React Native support
- 📱 Expo support
- ⚛️ NextJS support
- 🚯 Declarative cache lifetime policy
- 🧅 Composable middlewares
- 💽 Global data consistency guarantees
- 🏇 Automatic race condition elimination
- 👯 Global referential equality guarantees
- Todo: GitHub | Sandbox | Edit on CodeSandbox
- Github: GitHub | Sandbox
- NextJS: GitHub | Sandbox | Edit on CodeSandbox
- Websockets: GitHub | Sandbox | Website
-
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 | ✅ |