Skip to content

React components and hooks

rocambille edited this page Jun 4, 2026 · 2 revisions

Summary: This page documents how data reactivity works in StartER, leveraging React's cache system and the standard useMutate hook.

Routing and UI

Routing is handled by React Router. Each page is a React component associated with a path in src/react/routes.tsx. StartER uses a modern approach based on Promises and caching to manage data loading.

Data management

StartER separates data retrieval (Reading) from data modification (Writing).

Reading: use() and cache()

To fetch data, we advise to use React's use function with the cache utility function of StartER. These functions ensure that the same request is not sent repeatedly during re-renders.

Example in ItemList.tsx:

import { use } from "react";
import { cache } from "../../helpers/cache";

function ItemList() {
  // Suspends the component until data arrives
  const items = use(cache<Item[]>("/api/items"));

  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.title}</li>)}
    </ul>
  );
}

Writing: useMutate() and reactivity

The challenge for "data-driven" React applications is updating the interface after a modification (mutation). StartER solves this via DataRefreshContext and the useMutate hook.

The mutation lifecycle:

  1. Mutation: API call (POST/PUT/DELETE) via the apiMutate() function.
  2. Invalidation: removal of stale data from the cache via invalidateCache(). You can also clear the entire cache by using invalidateCache("*").
  3. Refresh: triggering a global refresh via a "tick" (counter) that forces React to re-suspend components and thus re-fetch fresh data.

The useMutate hook encapsulates these three steps:

// src/react/components/item/ItemCreate.tsx
const mutate = useMutate();

const addItem = async (partialItem) => {
  await mutate(
    "/api/items",     // URL
    "post",           // Method
    partialItem,      // Request body
    ["/api/items"]    // Paths to invalidate in cache
  );

  // Redirect or success message
};

DataRefreshContext

For this system to work, the application must be wrapped in a <DataRefreshProvider>. This is already configured in src/react/routes.tsx.

It provides a "global signaling" mechanism: as soon as a mutation succeeds, all components using cached (and invalidated) data will automatically update to display the most recent data.

Best practices

  • Atomic components: keep your UI components "pure". They should rely on use(cache(...)) for reading and useMutate() for writing.
  • Explicit invalidation: don't forget to pass the list of affected paths to useMutate so the user doesn't see stale data.

See also

Clone this wiki locally