data-client
Async State Management without the Management. REST, GraphQL, SSE, Websockets
Top Related Projects
🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.
React Hooks for Data Fetching
:rocket: A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
The official, opinionated, batteries-included toolset for efficient Redux development
Relay is a JavaScript framework for building data-driven React applications.
Quick Overview
Reactive Data Client is a comprehensive data fetching and caching solution for React applications. It provides a powerful and flexible API for managing remote data, optimizing network requests, and maintaining a consistent state across the application. The library is designed to work seamlessly with REST, GraphQL, and other data sources.
Pros
- Efficient caching and state management, reducing unnecessary network requests
- Automatic request deduplication and optimistic updates for improved performance
- Supports both REST and GraphQL APIs out of the box
- Integrates well with TypeScript for enhanced type safety
Cons
- Steeper learning curve compared to simpler data fetching libraries
- Requires more setup and configuration for complex use cases
- May be overkill for small projects or simple data fetching needs
- Documentation could be more comprehensive for advanced features
Code Examples
- Defining an endpoint:
import { Endpoint } from '@data-client/endpoint';
const UserDetail = new Endpoint(({ id }: { id: number }) => ({
url: `/users/${id}`,
schema: User,
}));
- Using a hook to fetch data:
import { useResource } from '@data-client/react';
function UserProfile({ id }: { id: number }) {
const user = useResource(UserDetail, { id });
return <div>{user.name}</div>;
}
- Optimistic updates:
import { useFetcher } from '@data-client/react';
function UpdateUserButton({ user }: { user: User }) {
const update = useFetcher(UserDetail);
const handleClick = () => {
update({ id: user.id }, { name: 'New Name' }, { optimisticUpdate: true });
};
return <button onClick={handleClick}>Update Name</button>;
}
Getting Started
- Install the package:
npm install @data-client/react @data-client/rest
- Set up the provider in your app:
import { CacheProvider } from '@data-client/react';
import { RestEndpoint } from '@data-client/rest';
function App() {
return (
<CacheProvider>
{/* Your app components */}
</CacheProvider>
);
}
- Define endpoints and use hooks in your components:
import { Endpoint } from '@data-client/endpoint';
import { useResource } from '@data-client/react';
const UserList = new Endpoint(() => ({
url: '/users',
schema: [User],
}));
function UserListComponent() {
const users = useResource(UserList);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
Competitor Comparisons
🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.
Pros of Query
- Wider adoption and larger community support
- More comprehensive documentation and examples
- Built-in devtools for easier debugging and visualization
Cons of Query
- Potentially higher bundle size due to more features
- Steeper learning curve for complex use cases
- Less focus on normalized caching compared to Data Client
Code Comparison
Query:
const { data, isLoading, error } = useQuery('todos', fetchTodos)
if (isLoading) return 'Loading...'
if (error) return 'An error occurred: ' + error.message
return (
<ul>{data.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>
)
Data Client:
const todos = useSuspense(TodoResource.getList())
return (
<ul>{todos.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>
)
Summary
Query offers a more mature ecosystem with extensive documentation and community support, making it easier for developers to get started and find solutions. However, Data Client provides a more streamlined API for normalized caching and may be more suitable for projects requiring efficient data management. The choice between the two depends on specific project requirements and developer preferences.
React Hooks for Data Fetching
Pros of SWR
- Simpler API with less boilerplate code
- Built-in support for real-time data updates
- Extensive documentation and large community support
Cons of SWR
- Less flexible caching mechanism
- Limited support for complex data normalization
- Fewer advanced features for managing large-scale applications
Code Comparison
SWR:
import useSWR from 'swr'
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
return <div>Hello {data.name}!</div>
}
Data Client:
import { useResource } from '@data-client/react';
function Profile() {
const user = useResource(User, { id: 1 });
if (!user) return <div>Loading...</div>
return <div>Hello {user.name}!</div>
}
Key Differences
- SWR focuses on simplicity and ease of use, while Data Client offers more advanced features for complex data management
- Data Client provides stronger typing and normalization capabilities, which can be beneficial for larger applications
- SWR has a larger community and more third-party integrations, making it easier to find solutions and extensions
Use Cases
- SWR: Ideal for smaller to medium-sized projects with straightforward data fetching needs
- Data Client: Better suited for large-scale applications with complex data relationships and advanced caching requirements
:rocket: A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
Pros of Apollo Client
- Widely adopted and battle-tested in production environments
- Extensive documentation and community support
- Seamless integration with Apollo Server and other Apollo ecosystem tools
Cons of Apollo Client
- Larger bundle size, which may impact application performance
- Steeper learning curve for developers new to GraphQL
- Can be overkill for simpler applications or those not fully committed to GraphQL
Code Comparison
Apollo Client:
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache()
});
const GET_USERS = gql`
query GetUsers {
users {
id
name
}
}
`;
Data Client:
const client = new DataClient({
baseUrl: 'https://api.example.com',
dataExpiryLength: 5 * 60 * 1000,
});
const usersEndpoint = new Endpoint({
urlPrefix: 'users',
schema: User,
});
Both libraries aim to simplify data fetching and state management in applications, but they take different approaches. Apollo Client is specifically designed for GraphQL, offering a comprehensive solution for GraphQL-based applications. Data Client, on the other hand, is more flexible and can work with various API types, including REST and GraphQL. Data Client focuses on performance and minimal bundle size, making it potentially more suitable for smaller projects or those with mixed API requirements.
The official, opinionated, batteries-included toolset for efficient Redux development
Pros of Redux Toolkit
- Widely adopted and well-established in the React ecosystem
- Comprehensive documentation and large community support
- Includes utilities for common Redux use cases, reducing boilerplate
Cons of Redux Toolkit
- Can be complex for simple applications
- Requires more setup and configuration compared to Data Client
- May lead to excessive re-renders if not optimized properly
Code Comparison
Redux Toolkit:
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: state => state + 1,
},
})
Data Client:
import { Entity } from '@data-client/rest';
class Counter extends Entity {
id = null;
value = 0;
increment() {
return { ...this, value: this.value + 1 };
}
}
Key Differences
- Redux Toolkit focuses on state management, while Data Client is designed for data fetching and caching
- Data Client provides a more object-oriented approach to data modeling
- Redux Toolkit requires explicit actions and reducers, whereas Data Client uses method calls on entities
Use Cases
- Redux Toolkit: Complex state management in large applications
- Data Client: API-driven applications with a focus on data normalization and caching
Both libraries have their strengths, and the choice depends on the specific needs of your project and team preferences.
Relay is a JavaScript framework for building data-driven React applications.
Pros of Relay
- Developed and maintained by Facebook, ensuring robust support and regular updates
- Tightly integrated with GraphQL, providing optimized data fetching and caching
- Offers powerful features like automatic query colocation and fragment composition
Cons of Relay
- Steeper learning curve due to its complex architecture and concepts
- Requires a specific GraphQL server setup, limiting flexibility with other API types
- Can be overkill for smaller projects or simpler data requirements
Code Comparison
Relay:
const RepositoryNameFragment = graphql`
fragment RepositoryName_repository on Repository {
name
}
`;
function RepositoryName(props) {
const data = useFragment(RepositoryNameFragment, props.repository);
return <h1>{data.name}</h1>;
}
Data Client:
const repository = useResource(RepositoryResource.detail(), { id });
return <h1>{repository.name}</h1>;
Key Differences
- Relay is specifically designed for GraphQL, while Data Client supports REST and GraphQL
- Data Client offers a simpler API and easier setup, making it more accessible for beginners
- Relay provides more advanced features for complex data requirements and large-scale applications
- Data Client has a smaller bundle size and potentially better performance for simpler use cases
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
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
Installation
npm install --save @data-client/react @data-client/rest @data-client/test
For more details, see the Installation docs page.
Usage
Simple TypeScript definition
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,
};
}
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>
);
Reactive Mutations
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
const price = useLive(PriceResource.get, { symbol });
return price.value;
Type-safe Imperative Actions
const ctrl = useController();
ctrl.expireAll(ArticleResource.getList);
ctrl.invalidate(ArticleResource.get, { id });
ctrl.invalidateAll(ArticleResource.getList);
ctrl.setResponse(ArticleResource.get, { id }, articleData);
ctrl.set(Article, { id }, articleData);
ctrl.fetchIfStale(ArticleResource.get, { id });
ctrl.fetch(ArticleResource.update, { id }, articleData);
Programmatic queries
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);
Powerful Middlewares
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();
}
}
Integrated data mocking
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
- 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
Examples
API
Reactive Applications
-
Rendering: useSuspense(), useLive(), useCache(), useDLE(), useQuery()
-
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, renderDataClient()
-
Middleware: LogoutManager, NetworkManager, SubscriptionManager, PollingSubscription, DevToolsManager
Define Data
Networking definition
Data Type | Mutable | Schema | Description | Queryable |
---|---|---|---|---|
Object | â | Entity, schema.Entity mixin | 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 | â |
Top Related Projects
🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.
React Hooks for Data Fetching
:rocket: A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
The official, opinionated, batteries-included toolset for efficient Redux development
Relay is a JavaScript framework for building data-driven React applications.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot