Skip to content

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 }, where draft exposes displayValue, pendingValue, isPending, set, push, and discard.

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>
  );
}