Skip to main content

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 a switchAccount 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 and error 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 needsReauth is 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 an AccountTransitionError, 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.
Prompting a re-auth on every rejection tells a user with no signal that their account is dead, and does it again every time they lose signal — which is what this discriminator exists to prevent. 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.

Integration Guide

For multi-account integration guidance, see Multi-Account.