Overview
useSwitchAccount switches the active session to a different stored account. It exchanges the target account’s stored refresh token for a live session first, and only then clears the current auth state and swaps the new one in. If that exchange fails, nothing is touched — you stay signed in where you were.
Usage Example
Parameters
The hook returns aswitchAccount function that accepts:
string
required
The ID of the account to switch to. Must be present in the stored accounts map.
Returns
function
Async function that switches the active session. It rejects without doing
anything when the id is not in the accounts map — with an
AccountTransitionError carrying accountNotFound: true — or when no
projectId is available, and it rejects if the session for the target account
could not be established. See below. Its signature is
({ userId }) => Promise<void>, so nothing warns you at compile time:
await switchAccount(...) without a catch becomes an unhandled rejection.Calling it with the ID of the already-active account is a no-op only while
that account has a live session. When the selection names an account with no
session — after a refusal at the account limit, for instance — re-selecting it
runs the full transition, so the user has a way back in without restarting the
app.boolean
true while the switch is in progress (validating the target’s stored
credential, then swapping the session over).string | null
Error message if the switch failed, or
null if no error occurred.Failure Behavior
When the target account’s stored refresh token cannot be exchanged for an access token — it expired, was revoked, or the stored entry has no usable token at all — the returned promise rejects anderror is set.
A failed switch leaves the current session completely intact. The target’s
credential is validated out of band before anything is torn down, so on failure
nothing has changed: the account you were using is still active, still holds its
tokens, and still has its cached data. Only the rejection happened.
- The previously active account is still active, with a live session. Nothing was signed out.
- Both entries remain in the accounts map, including the one that failed. That entry is what lets you render a “session expired” prompt for it.
- When the server was the one that refused the credential, that account’s
needsReauthis set, so the switcher can label it without trying again. A switch that failed because the request never reached the server does not set it. - Switching with no account active — the account-picker state a user lands in after signing out — leaves nothing selected and marks the app signed out, so the next launch does not silently activate a different account.
Telling a dead account from a dead network
A transition failure rejects with anAccountTransitionError, and its two boolean discriminants
are the only way to tell those failures apart — so check with instanceof first, as the example
above does, rather than assuming the type. (The hook also throws a plain Error when no
projectId is configured, and removeAccount rejects with the
server’s own error, unchanged, when it refuses to unbind that account’s push notifications.)
boolean
true when the server refused the stored credential — expired, revoked, reuse-detected,
invalidated by a password change or a remote sign-out-all — or when the stored entry carries no
usable credential at all. This is exactly the case in which needsReauth is set.false when the exchange never got an answer, or the rotated successor could not be persisted.
Nothing is marked and the same account may work on the next attempt.boolean
true when the userId is not in the accounts map at all — a stale id, typically from a
switcher rendered off a snapshot the map has since moved past. No network call went out, nothing
was marked, and no session was touched. Re-read the list from
useAccounts rather than prompting a sign-in for an account that is
not there.Never true at the same time as credentialRejected; both are false for a transport failure.AccountTransitionError and ACCOUNT_TRANSITION_FAILED_MESSAGE (its default message, used when
the server gave no reason) are both exported.
The switch costs exactly one token exchange. The refresh endpoint rotates
the token it is given, and validating the target’s credential is that
exchange — it is not an extra probe in front of one.

