| name | data-client-react |
| description | Use @data-client/react hooks for data fetching, mutations, and rendering - useSuspense, useFetch, useQuery, useCache, useLive, useDLE, useSubscription, useController, DataProvider, AsyncBoundary, useLoading, useDebounce. Use when reading/rendering remote data, triggering mutations, doing optimistic updates, real-time subscriptions, or wiring Suspense/error boundaries in React. |
| license | Apache 2.0 |
Rendering
const todo = useSuspense(TodoResource.get, { id: 5 });
const todoList = useSuspense(TodoResource.getList);
const todoListByUser = useSuspense(TodoResource.getList, { userId: 1 });
const todo = useLive(TodoResource.get, { id: 5 });
const todo = useCache(TodoResource.get, { id: 5 });
const todo = useQuery(Todo, { id: 5 });
const { data, loading, error } = useDLE(TodoResource.get, { id: 5 });
useSubscription(TodoResource.get, { id: 5 });
const postPromise = useFetch(PostResource.get, { id });
const commentsPromise = useFetch(CommentResource.getList, { postId: id });
const post = use(postPromise);
const comments = use(commentsPromise);
For API definitions (like TodoResource), apply the skill "data-client-rest".
Mutations
const ctrl = useController();
const updateTodo = todo => ctrl.fetch(TodoResource.update, { id }, todo);
const partialUpdateTodo = todo =>
ctrl.fetch(TodoResource.partialUpdate, { id }, todo);
const addTodoToBeginning = todo =>
ctrl.fetch(TodoResource.getList.unshift, todo);
const addTodoToEnd = todo => ctrl.fetch(TodoResource.getList.push, { userId: 1 }, todo);
const deleteTodo = id => ctrl.fetch(TodoResource.delete, { id });
const getNextPage = (page) => ctrl.fetch(TodoResource.getList.getPage, { userId: 1, page })
Helpful hooks
const [handleSubmit, loading, error] = useLoading(
async data => {
const post = await ctrl.fetch(PostResource.getList.push, data);
navigateToPost(post.id);
},
[ctrl],
);
const [query, setQuery] = React.useState('');
const handleChange = e => setQuery(e.currentTarget.value);
const [debouncedQuery, isPending] = useDebounce(query, 200);
return (
<AsyncBoundary fallback={<Loading />}>
<IssueList query={debouncedQuery} owner="facebook" repo="react" />
</AsyncBoundary>
)
Components
Prefer using AsyncBoundary for error handling and loading states unless the codebase has
a custom AsyncBoundary that already combines Suspense and ErrorBoundary.
Its props are fallback, errorComponent, and errorClassName and listen. It can be used to wrap any component that fetches data.
<AsyncBoundary listen={history.listen}>
<TodoList />
</AsyncBoundary>
Type-safe imperative actions
Controller is returned from useController(). It has:
ctrl.fetch(), ctrl.fetchIfStale(), ctrl.expireAll(), ctrl.invalidate(), ctrl.invalidateAll(), ctrl.setResponse(), ctrl.set(),
ctrl.setError(), ctrl.resetEntireStore(), ctrl.subscribe(), ctrl.unsubscribe().
Programmatic queries
const queryRemainingTodos = new Query(
TodoResource.getList.schema,
entries => entries.filter(todo => !todo.completed).length,
);
const allRemainingTodos = useQuery(queryRemainingTodos);
const firstUserRemainingTodos = useQuery(queryRemainingTodos, { userId: 1 });
const groupTodoByUser = new Query(
TodoResource.getList.schema,
todos => Object.groupBy(todos, todo => todo.userId),
);
const todosByUser = useQuery(groupTodoByUser);
Browser Debugging (Chrome DevTools MCP)
To inspect store state, track dispatched actions, or invoke
Controller methods from a browser MCP (user-chrome-devtools),
see devtools-debugging. Uses globalThis.__DC_CONTROLLERS__
available in dev mode.
Managers
Custom Managers allow for global side effect handling.
This is useful for webosckets, SSE, logging, etc. Always use the skill "data-client-manager" when writing managers.
Best Practices & Notes
References
For detailed API documentation, see the references directory:
ALWAYS follow these patterns and refer to the official docs for edge cases. Prioritize code generation that is idiomatic, type-safe, and leverages automatic normalization/caching via skill "data-client-schema" definitions.