Auth and Subsystem Status
The React SDK separates auth status from state data:
useAuth()returns auth status and auth actions.usePublicState()with no arguments returns public-state subsystem status.useUserState()with no arguments returns user-state subsystem status.- Keyed
usePublicState(key)returns{ value }. - Keyed
useUserState(key)returns{ value, set, draft }, wheredraftexposesdisplayValue,pendingValue,isPending,set,push, anddiscard.
All hooks must be called inside a component that is a descendant of SkyStateProvider.
useAuth
typescript
function useAuth(): UseAuthResult
type UseAuthResult =
| {
status: 'unauthenticated';
loginWithRedirect: () => Promise<void>;
logout: () => Promise<void>;
}
| {
status: 'authenticating';
loginWithRedirect: () => Promise<void>;
logout: () => Promise<void>;
}
| {
status: 'authenticated';
idToken: string;
claims: Readonly<Record<string, unknown>>;
error?: AuthError;
loginWithRedirect: () => Promise<void>;
logout: () => Promise<void>;
};tsx
import { useAuth } from '@skystate/react';
export function LoginButton() {
const { status, loginWithRedirect, logout } = useAuth();
const isAuthenticated = status === 'authenticated';
return (
<button onClick={isAuthenticated ? logout : loginWithRedirect}>
{isAuthenticated ? 'Log out' : 'Log in'}
</button>
);
}Use the authenticated branch before reading idToken or claims.
logout() is asynchronous because it calls the API to revoke the current refresh-token family before clearing local state. The lower-level core clearAuthTokens() API is local-only token deletion; React apps should prefer useAuth().logout() for user sign-out.
Subsystem Status
typescript
type SubsystemStatus =
| { status: 'idle' | 'loading' | 'ready' }
| { status: 'error'; error: SkyStateError };tsx
import { usePublicState, useUserState } from '@skystate/react';
export function StatusBar() {
const publicState = usePublicState();
const userState = useUserState();
if (publicState.status === 'error') {
return <div>Public state failed: {publicState.error.message}</div>;
}
if (userState.status === 'error') {
return <div>User state failed: {userState.error.message}</div>;
}
return (
<div>
Public: {publicState.status}; User: {userState.status}
</div>
);
}The no-argument hooks never expose value, set, or draft. Keyed hooks never expose subsystem status or error.
Auth Errors and onError
The SkyStateProvider accepts an optional onError callback for provider, subsystem, and auth-pipeline errors. When onError is omitted, development builds log a fallback warning.
tsx
<SkyStateProvider
account="acc_example"
project="my-app"
environment="production"
onError={(err) => {
console.error(err.code, err.message);
}}
>
<App />
</SkyStateProvider>Token refresh failures
Temporary sign-in renewal failures keep the current session active while the SDK works to recover. Your app may see an advisory error field on the authenticated snapshot; use onError if you want to show a non-blocking warning.
Expired or invalid sessions transition to unauthenticated, so the user can sign in again.
Storage write failure
If the token storage adapter cannot persist tokens during setAuthTokens() or PKCE exchange, the in-memory session continues but the session will not survive a page reload. The onError callback fires once with err.code === 'storage_write_failed'. Use this to warn the user.
tsx
onError={(err) => {
if (err.code === 'storage_write_failed') {
showToast('Session will not persist after reload - storage unavailable.');
}
}}You can also check auth.error.code === 'storage_write_failed' on the authenticated snapshot directly.
Authorization failures (403)
A permission error on user-state load or save is reported as an authorization error. Check that the provider is mounted with the right account, project, and environment, and that the project's auth-provider settings allow the signed-in user.
User State with Auth
tsx
import { useAuth, useUserState } from '@skystate/react';
export function Preferences() {
const auth = useAuth();
const userState = useUserState();
const { value: theme, set: setTheme } = useUserState('theme', 'dark');
if (auth.status === 'authenticating') return null;
if (auth.status !== 'authenticated') {
return <button onClick={auth.loginWithRedirect}>Sign in</button>;
}
if (userState.status === 'loading') return <Skeleton />;
if (userState.status === 'error') return <div>{userState.error.message}</div>;
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Theme: {theme}
</button>
);
}