useClient
useClient():
Client
Defined in: react/src/index.tsx:528
The client of the nearest ReactorProvider: the one object that builds canister handles and query options, and that signs users in and out.
Call it in the body of each component that builds keys or options, on
every render, and build them from what it returns there, never from a
client held at module scope: they are built for the caller this render
shows. Do not keep what it returns past the render: while a page hydrates
it is a view for the anonymous caller, and a view never moves on. An effect
or a callback that uses it closes over its render’s value and lists it in
its dependencies (react-hooks/exhaustive-deps), so that it runs again with
the client after hydrating. Kept from the hydrating render anywhere else
(useState(client), useRef(client), a useMemo or useCallback with
[], a module variable), it stays that view: the component shows the
anonymous caller’s data, a read it starts while the user is signed in is
cancelled, and a write through it still signs as the live caller. Nothing
warns about it. A nested ReactorProvider given it
(client={() => client}) holds the client the view was made over.
It follows the client’s caller with useSyncExternalStore, so a
component that calls it renders again when the caller changes (a sign-in, a
switch of account, a sign-out) and never otherwise: a change of status that
leaves the caller as it is (a session that expired, or one signed in
elsewhere, both call as the anonymous principal) renders nothing. In the
steady state it returns the provider’s client object itself.
On a server, and while a page hydrates, the caller is the anonymous one, as
for useAuth. In a browser that holds a session, the hydrating render
gets a view of the client for the anonymous caller: its queryKey,
queryOptions, caller() and authState() are the anonymous caller’s, so
the page finds what the server prefetched and dehydrated and matches its
HTML; everything else (canisters, mutationOptions, signIn, signOut,
the QueryClient) is the client’s own, and a write signs as the caller
current when it runs. Right after hydrating, React renders the component
again with the client itself, for the user: a read with none of the user’s
data yet shows its loading state, and a useSuspenseQuery read its
boundary’s fallback, until that data arrives. A client built with
identity keeps its caller for good and is always returned as it is.
Three consequences of that move on a signed-in reload:
- A Suspense boundary that is still dehydrated below a component that
renders with the caller (this hook or useAuth), because its lazy
code is still loading or its streamed HTML has not arrived, is rendered
on the client when that component moves on: it shows its fallback
instead of the server’s HTML, and React 18 reports a recoverable error.
Its data is still the user’s. A
useAuth()component also moves on in a tab whose session expired or is signed in elsewhere. Render such a boundary where no component that renders with the caller sits above it, or pass it in aschildren, which a component’s own update does not render again. - A read the hydrating render built may still run once (TanStack Query
refetches stale data on mount, and an effect may fetch with the view’s
options). It is cancelled before anything is sent. A key that holds data
keeps it as it was, and a fetch of it resolves with that data. A key with
none fails (
kind"cancelled",code"caller_changed") until the anonymous caller is current again: the hydrating render’s own reads never show that, but a listener ofclient.queryClient.getQueryCache().subscribe()(the client takes noQueryCacheof yours), an effect that awaits the fetch and, for auseSuspenseQueryread the server rendered without dehydrating its data, React’sonRecoverableError(as the reported error’scauseon React 19) see it, so ignorekind"cancelled"there. - Put a Suspense boundary above every component that reads with
useSuspenseQuery, on React 18 and 19 alike. The move is a synchronous update, which such a read suspends until the user’s data arrives. With a boundary above, the boundary shows its fallback, then the user’s data, and the rest of the page responds meanwhile. With none, React 18 refuses the update (“A component suspended while responding to synchronous input”) and unmounts the root, so a signed-in reload renders nothing. React 19 keeps the server’s HTML on screen, but until the user’s data arrives, however long the read and its retries take, a click or any other update outside a transition commits nothing, and neither does a transition that renders the reading component again.
Returns
Section titled “Returns”Throws
Section titled “Throws”Error outside a ReactorProvider, naming it.