Skip to main content

data-client-react

Read, change and render async and shared state in React with @data-client/react: any data from an API or other async source, and global state shared across components (it has to persist somewhere, which makes it async). Use it for loading and error states, mutations and forms, filtered or derived views, and debugging data that looks wrong or stale on screen. Use it in place of fetch in useEffect, or app-wide useState and context; its main hooks are useSuspense, useQuery and useController.

Source facts

Repository
reactive/data-client
Last source activity
October 29, 2026 at 02:37
Detected SKILL.md language
English
Stars
2,039
Forks
98

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
29 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
data-client-react
description
Read, change and render async and shared state in React with @data-client/react: any data from an API or other async source, and global state shared across components (it has to persist somewhere, which makes it async). Use it for loading and error states, mutations and forms, filtered or derived views, and debugging data that looks wrong or stale on screen. Use it in place of fetch in useEffect, or app-wide useState and context; its main hooks are useSuspense, useQuery and useController.
license
Apache 2.0
<!-- Generated by `yarn build:skills` from .agents/skill-sources/data-client-framework.md. Edit the source, not this file. --> ## Rendering ```ts // GET https://jsonplaceholder.typicode.com/todos/5 const todo = useSuspense(TodoResource.get, { id: 5 }); // GET https://jsonplaceholder.typicode.com/todos const todoList = useSuspense(TodoResource.getList); // GET https://jsonplaceholder.typicode.com/todos?userId=1 const todoListByUser = useSuspense(TodoResource.getList, { userId: 1 }); // subscriptions with polling, websockets or SSE const todo = useLive(TodoResource.get, { id: 5 }); // without fetch const todo = useCache(TodoResource.get, { id: 5 }); const todo = useQuery(Todo, { id: 5 }); // fetch without Suspense - returns { data, loading, error } const { data, loading, error } = useDLE(TodoResource.get, { id: 5 }); // subscribe without Suspense (use with useSuspense or useDLE) useSubscription(TodoResource.get, { id: 5 }); // parallel fetches with React.use() const postPromise = useFetch(PostResource.get, { id }); const commentsPromise = useFetch(CommentResource.getList, { postId: id }); const post = use(postPromise); const comments = use(commentsPromise); ``` Sequential `useSuspense` calls waterfall, so load 2+ endpoints with `useFetch` as above. Before React 19, read them with `useSuspense` instead of `use()`. For API definitions (like TodoResource), apply the skill "data-client-rest". ## Mutations ```ts const ctrl = useController(); // PUT https://jsonplaceholder.typicode.com/todos/5 const updateTodo = todo => ctrl.fetch(TodoResource.update, { id }, todo); // PATCH https://jsonplaceholder.typicode.com/todos/5 const partialUpdateTodo = todo => ctrl.fetch(TodoResource.partialUpdate, { id }, todo); // POST https://jsonplaceholder.typicode.com/todos const addTodoToBeginning = todo => ctrl.fetch(TodoResource.getList.unshift, todo); // POST https://jsonplaceholder.typicode.com/todos?userId=1 const addTodoToEnd = todo => ctrl.fetch(TodoResource.getList.push, { userId: 1 }, todo); // DELETE https://jsonplaceholder.typicode.com/todos/5 const deleteTodo = id => ctrl.fetch(TodoResource.delete, { id }); // GET https://jsonplaceholder.typicode.com/todos?userId=1&page=2 const getNextPage = (page) => ctrl.fetch(TodoResource.getList.getPage, { userId: 1, page }) ``` ## Helpful hooks ```tsx const [handleSubmit, loading, error] = useLoading( async data => { const post = await ctrl.fetch(PostResource.getList.push, data); navigateToPost(post.id); }, [ctrl], ); ``` ```tsx 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="react" repo="react" /> </AsyncBoundary> ) ``` ## Components Prefer using [AsyncBoundary](references/AsyncBoundary.md) for error handling and loading states unless the codebase has a custom AsyncBoundary that already combines Suspense and ErrorBoundary. Its props are `fallback`, `errorComponent`, `errorClassName`, and `listen`. It can be used to wrap any component that fetches data. ```tsx <AsyncBoundary listen={history.listen}> <TodoList /> </AsyncBoundary> ``` ## Type-safe imperative actions [Controller](references/Controller.md) 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(). | To refresh or update | Call | |---|---| | You already have the new value | `ctrl.set()` / `ctrl.setResponse()` (no network) | | One endpoint+args | `ctrl.fetch(endpoint, args)` | | All cached args of an endpoint | `ctrl.expireAll(endpoint)`: fetching hooks refetch; `useCache`/`useQuery` don't | | Current data must not show while refetching | `ctrl.invalidate()` / `ctrl.invalidateAll()`: `useSuspense` suspends | Write many entities without a fetch with one `ctrl.set([Entity], rows)`. Never loop `ctrl.set(Entity, args, row)` per row, and never add an endpoint or `setResponse()` just to batch. ## Programmatic queries ```ts const queryRemainingTodos = new Query( TodoResource.getList.schema, entries => entries.filter(todo => !todo.completed).length, ); const allRemainingTodos = useQuery(queryRemainingTodos); const firstUserRemainingTodos = useQuery(queryRemainingTodos, { userId: 1 }); ``` ```ts const groupTodoByUser = new Query( TodoResource.getList.schema, todos => Object.groupBy(todos, todo => todo.userId), ); const todosByUser = useQuery(groupTodoByUser); ``` --- ## Debugging wrong or stale data in a running app When the running app shows the wrong data, inspect the page's store before reading code for the cause. In dev mode, `__DC__.controller` is the page's Controller and `__DC__.actions` its recent [actions](references/Actions.md), reachable from any browser tool that runs JavaScript in the page. [devtools-debugging](references/devtools-debugging.md) maps symptoms to what to check and links the setup for your browser tool. ## Managers Custom [Managers](https://dataclient.io/docs/api/Manager) allow for global side effect handling. This is useful for websockets, SSE, logging, etc. Always use the skill "data-client-manager" when writing managers. ## Best Practices & Notes - [useDebounce(query, timeout)](references/useDebounce.md) when rendering async data based on user field inputs - [[handleSubmit, loading, error] = useLoading()](references/useLoading.md) when tracking async mutations - Prefer smaller React components that do one thing - **Co-locate data bindings**: call useSuspense/useDLE/useCache/useQuery in the component that renders the data — don't prop drill. Pass ids/args, not fetched data a component only hands down (a list passing each item to its row is fine); bindings with the same endpoint and args share one fetch and store entry - **Don't hide data bindings inside custom hooks**: wrapping them obfuscates a component's data dependencies and couples data logic to view code, causing drift. Put tightly coupled data transformations in a `Query` schema (with the data model, e.g. `src/resources/`) so they stay reusable and evolve independently of views - `undefined` from `useCache`, or `useQuery` on an Entity, means "not loaded yet", not "doesn't exist"; a deep link or refresh starts empty. Read required data with `useSuspense`/`useDLE`, which fetch only when the cache is missing, stale or invalidated; use `useCache`/`useQuery` for data other code loads - For tests, apply the skill "data-client-react-testing" # References For detailed API documentation, see the [references](references/) directory: - [useSuspense](references/useSuspense.md);[_pagination.md](references/_pagination.md) - Fetch with Suspense - [useFetch](references/useFetch.md) - Fetch for React.use() and parallel loading - [useQuery](references/useQuery.md) - Read from cache without fetch - [useCache](references/useCache.md) - Read from cache (nullable) - [useLive](references/useLive.md);[_useLive.md](references/_useLive.md) - Fetch + subscribe to updates - [useDLE](references/useDLE.md) - Fetch without Suspense (returns data/loading/error) - [useSubscription](references/useSubscription.md) - Subscribe to updates (polling/websocket/SSE) - [useController](references/useController.md) - Access Controller - [Controller](references/Controller.md) - Imperative actions - [AsyncBoundary](references/AsyncBoundary.md);[_AsyncBoundary.md](references/_AsyncBoundary.md) - Error/loading boundary - [useLoading](references/useLoading.md);[_useLoading.md](references/_useLoading.md) - Track async mutation state - [useDebounce](references/useDebounce.md) - Debounce values - [DataProvider](references/DataProvider.md) - Root provider - [data-dependency](references/data-dependency.md) - Rendering guide - [mutations](references/mutations.md);[_VoteDemo.md](references/_VoteDemo.md) - Mutations guide - [Actions](references/Actions.md) - Store action types (FETCH, SET, etc.) - [devtools-debugging](references/devtools-debugging.md) - Debug the running app from a browser tool - [browser-cursor.md](references/browser-cursor.md);[browser-chrome-devtools-mcp.md](references/browser-chrome-devtools-mcp.md);[browser-playwright-mcp.md](references/browser-playwright-mcp.md);[browser-claude-in-chrome.md](references/browser-claude-in-chrome.md) - Setup for one browser tool; read only yours **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.**
View on GitHub