You will learn
This page is a server component. It fetches on the server, where the query and its credentials stay, and the client builds the store from what arrives. Nothing is fetched twice and no loading state is spent on data the server already had.
The server starts this fetch and does not await it. React serializes the promise across the boundary, so the client receives a thenable and puts it straight in as the store’s initial value. The shell is sent immediately with a fallback, and the rows arrive later in the same response.
// A server component. Note the missing await.
<StreamedClient rows={fetchAccounts()} />
// A client component.
const [store] = useState(() => createStore(rows));
const data = use(useStore(store));
Then press Refetch in a Transition. The fetch is held open, so you can sit in the in-flight state: the column says the Transition is pending and dims, and the rows you were reading stay on screen instead of being replaced by a fallback. Press New rows arrive to let it finish.
use(useStore(store))streamedThese rows arrived as a promise the server never awaited. Batch 1.
When the data is quick, await it in the server component and pass plain rows. The store is created from them on the client and everything on top — the filter, the counters — is ordinary client state.
useStore(store)client componentThe store is built here, on the client. The rows arrived as plain data and the filter is ordinary client state on top.
createStore is not a Hook. It is a plain factory, and it works in a server component — this summary was built by a store that was created, dispatched to and read during this render, on the server:
import { createStore } from "react-concurrent-store/store";
export default async function Page() {
const totals = createStore(
{ accounts: 0, orders: 0 },
(state, row) => ({
accounts: state.accounts + 1,
orders: state.orders + row.orders,
}),
);
for (const row of await fetchAccounts()) totals.dispatch(row);
// 4 accounts, 732 orders
}
Import it from react-concurrent-store/store. The main entry also exports useStore, which is a Hook, so it carries a "use client" directive and cannot be pulled into a server graph.