CtrlK
BlogDocsLog inGet started
Tessl Logo
SkillPublic

data-client-react

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.

76

Quality

96%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Rendering

// 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

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

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="react" 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, 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().

To refresh or updateCall
You already have the new valuectrl.set() / ctrl.setResponse() (no network)
One endpoint+argsctrl.fetch(endpoint, args)
All cached args of an endpointctrl.expireAll(endpoint): fetching hooks refetch; useCache/useQuery don't
Current data must not show while refetchingctrl.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

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)

When debugging a running app with a browser MCP (user-chrome-devtools), such as data that never appears, reverts, or refetches too often, read devtools-debugging before probing: its "Where to Start" table maps symptoms to the state and actions to check. Dev mode only: start with its debug shim, which every other snippet there depends on.

Managers

Custom Managers 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) when rendering async data based on user field inputs
  • [handleSubmit, loading, error] = useLoading() 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 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.

Repository

reactive/data-client

Path

.agents/skills/data-client-react/SKILL.md

Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.