npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@archie/react-sdk

v0.1.1

Published

Archie BAAS - React hooks, provider & components

Readme

@archie/react-sdk

React hooks, provider, and components for the Archie BAAS platform.

Built on top of @archie/js-sdk, this package provides a fully reactive React integration with auth state management, declarative data fetching, real-time subscriptions, file uploads with progress tracking, and route guards.


Table of Contents


Installation

npm install @archie/js-sdk @archie/react-sdk
# or
pnpm add @archie/js-sdk @archie/react-sdk

Peer dependencies: react >=18, @archie/js-sdk


Quick Start

import { createClient } from '@archie/js-sdk';
import { ArchieProvider, useAuth, useQuery, AuthGuard } from '@archie/react-sdk';

const archie = createClient({
  projectId: 'your-project-uuid',
  apiKey: 'anon your-api-key',
  apiUrl: 'https://your-project.archiecore.com',
  environment: 'master',
  retries: 3, // automatic retry with backoff (inherited from js-sdk)
  timeout: 15_000, // request timeout in ms
});

function App() {
  return (
    <ArchieProvider client={archie}>
      <AuthGuard fallback={<LoginPage />} loadingComponent={<Spinner />}>
        <Dashboard />
      </AuthGuard>
    </ArchieProvider>
  );
}

function LoginPage() {
  const { signIn } = useAuth();

  const handleSubmit = async (email: string, password: string) => {
    const { session, error } = await signIn({ email, password });
    if (error) console.error(error.message);
  };

  return <form onSubmit={/* ... */}>/* ... */</form>;
}

function Dashboard() {
  const { user, signOut } = useAuth();
  const { data, isLoading } = useQuery<{ orders: Order[] }>('{ orders { id total status } }');

  return (
    <div>
      <p>Welcome, {user?.email}</p>
      <button onClick={signOut}>Sign Out</button>
      {isLoading ? <Spinner /> : <OrderList orders={data?.orders ?? []} />}
    </div>
  );
}

ArchieProvider

Wraps your React app to provide the Archie client and auth session state via context.

import { createClient } from '@archie/js-sdk';
import { ArchieProvider } from '@archie/react-sdk';

const archie = createClient({ projectId: '...', apiKey: 'anon ...' });

function App() {
  return (
    <ArchieProvider client={archie}>
      <MyApp />
    </ArchieProvider>
  );
}

Props:

| Prop | Type | Description | | ---------- | -------------- | ------------------------------------- | | client | ArchieClient | Client instance from createClient() | | children | ReactNode | Child components |

Internals:

  • Creates React context with ArchieClient instance
  • Calls client.auth.waitForInit() on mount to restore persisted session
  • Subscribes to onAuthStateChange and updates session in context
  • Provides { client, session, isLoading } to all child hooks

Hooks

useArchieClient

Returns the raw ArchieClient instance. Useful for advanced use cases outside the provided hooks.

const archie = useArchieClient();

// Direct access to any module
const jwks = await archie.auth.getJWKS();

Throws if used outside <ArchieProvider>.


useAuth

Full authentication state and operations. All async methods return { data/session, error } — they never throw.

const {
  user, // User | null
  session, // Session | null
  isLoading, // boolean — true until initial session check completes
  isAuthenticated, // boolean — shorthand for session !== null
  signIn, // (params) => Promise<{ session, error }>
  signUp, // (params) => Promise<{ data, error }>
  signOut, // () => Promise<void>
  confirmSignUp, // (params) => Promise<{ session, error }>
  recoverPassword, // (params) => Promise<{ data, error }>
  resetPassword, // (params) => Promise<{ data, error }>
  refreshSession, // () => Promise<{ session, error }>
} = useAuth();

Example — Sign In:

function LoginForm() {
  const { signIn, isLoading } = useAuth();
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    const { session, error } = await signIn({ email, password });
    if (error) {
      setError(error.message);
    }
    // session is automatically updated in context — no manual setState needed
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && <p className="error">{error}</p>}
      {/* inputs */}
      <button disabled={isLoading}>Sign In</button>
    </form>
  );
}

Example — Sign Up flow:

const { signUp, confirmSignUp } = useAuth();

// Step 1: Register
const { data, error } = await signUp({ email, password });
// data = { userId: '...', message: 'Check your email' }

// Step 2: Confirm with code from email
const { session, error: confirmError } = await confirmSignUp({
  email,
  code: '123456',
});

Example — Password Recovery:

const { recoverPassword, resetPassword } = useAuth();

// Step 1: Request reset
await recoverPassword({ email });

// Step 2: Reset with code
await resetPassword({ email, code: '123456', newPassword: 'newPass!' });

All methods are memoized with useCallback — safe to pass as props without causing re-renders.


useQuery

Declarative GraphQL queries with caching, stale-while-revalidate, polling, and automatic deduplication.

const { data, error, isLoading, isRefetching, refetch } = useQuery<T>(
  gql,         // GraphQL query string
  variables?,  // Record<string, unknown>
  options?,    // UseQueryOptions
);

Options:

| Option | Type | Default | Description | | ---------------------- | ------------------------------ | ------- | ----------------------------------------------- | | enabled | boolean | true | Set false to skip fetch (conditional queries) | | refetchInterval | number | — | Poll interval in ms | | refetchOnWindowFocus | boolean | false | Refetch when window regains focus | | onSuccess | (data: T) => void | — | Called on successful fetch | | onError | (error: ArchieError) => void | — | Called on error |

Return:

| Field | Type | Description | | -------------- | --------------------- | -------------------------- | | data | T \| null | Query result | | error | ArchieError \| null | Error if failed | | isLoading | boolean | True on initial load | | isRefetching | boolean | True on background refetch | | refetch | () => Promise<void> | Manually trigger refetch |

Behaviors:

  • Stale-while-revalidate: Shows cached data immediately, refetches in background
  • Deduplication: Multiple components with the same query+variables share a single network request
  • Auto-refetch on auth change: When the user's token changes, all active queries refetch automatically
  • Variable tracking: Deep comparison via JSON.stringify — refetches when variables change
  • SSR safe: No window access when refetchOnWindowFocus is disabled

Examples:

// Basic query
const { data, isLoading } = useQuery<{ users: User[] }>('{ users { id email name } }');

// With variables
const { data } = useQuery<{ user: User }>('query($id: ID!) { user(id: $id) { id email } }', {
  id: userId,
});

// Conditional query (wait for userId)
const { data } = useQuery<{ user: User }>(
  'query($id: ID!) { user(id: $id) { id email } }',
  { id: userId },
  { enabled: !!userId },
);

// Polling every 5 seconds
const { data } = useQuery<{ stats: Stats }>('{ stats { activeUsers requests } }', undefined, {
  refetchInterval: 5000,
});

useMutation

Imperative GraphQL mutations with cache invalidation.

const { mutate, mutateAsync, data, error, isLoading, reset } = useMutation<T>(
  gql,       // GraphQL mutation string
  options?,  // UseMutationOptions
);

Options:

| Option | Type | Description | | ------------------- | ------------------------------ | ------------------------------------------------------- | | onSuccess | (data: T) => void | Called after successful mutation | | onError | (error: ArchieError) => void | Called on error | | onSettled | () => void | Called after success or error | | invalidateQueries | string[] | Query substrings to invalidate from cache after success |

Return:

| Field | Type | Description | | ------------- | ------------------------------- | -------------------------- | | mutate | (vars?) => void | Fire-and-forget mutation | | mutateAsync | (vars?) => Promise<T \| null> | Await the result | | data | T \| null | Last successful result | | error | ArchieError \| null | Last error | | isLoading | boolean | True while executing | | reset | () => void | Clear data/error/isLoading |

Example:

const { mutate, isLoading } = useMutation<{ createUser: User }>(
  'mutation($input: CreateUserInput!) { createUser(input: $input) { id email } }',
  {
    invalidateQueries: ['users'],
    onSuccess: (data) => toast.success(`Created ${data.createUser.email}`),
  },
);

const handleCreate = () => {
  mutate({ input: { email: '[email protected]', name: 'John' } });
};

useSubscription

Real-time GraphQL subscriptions via WebSocket. Subscribes on mount, unsubscribes on unmount.

const { data, error, connectionState } = useSubscription<T>(
  gql,         // GraphQL subscription string
  variables?,  // Record<string, unknown>
  options?,    // UseSubscriptionOptions
);

Options:

| Option | Type | Default | Description | | --------- | ------------------------------ | ------- | -------------------------------- | | enabled | boolean | true | Set false to skip subscription | | onData | (data: T) => void | — | Called on each new value | | onError | (error: ArchieError) => void | — | Called on error |

Return:

| Field | Type | Description | | ----------------- | --------------------- | ----------------------------------------------------------------- | | data | T \| null | Latest subscription value | | error | ArchieError \| null | Last error | | connectionState | ConnectionState | 'DISCONNECTED' \| 'CONNECTING' \| 'CONNECTED' \| 'RECONNECTING' |

Example:

function LiveOrders() {
  const { data, connectionState } = useSubscription<{ orderUpdated: Order }>(
    'subscription { orderUpdated { id status total } }',
  );

  return (
    <div>
      <Badge>{connectionState}</Badge>
      {data && <OrderCard order={data.orderUpdated} />}
    </div>
  );
}

Lifecycle: Reconnection is handled automatically by the underlying js-sdk realtime module (exponential backoff).


useFileUpload

File uploads with progress tracking.

const { upload, progress, isUploading, data, error, reset } = useFileUpload();

Return:

| Field | Type | Description | | ------------- | ----------------------------------------------------------------- | ---------------------------- | | upload | (file: File \| Blob, opts?: FileUploadOptions) => Promise<void> | Start upload | | progress | number | 0–100 | | isUploading | boolean | True while uploading | | data | FileUploadResult \| null | { url, fileId } on success | | error | ArchieError \| null | Error if failed | | reset | () => void | Clear all state |

FileUploadOptions: { filename?, contentType?, providerType?, onProgress? }

Example:

function FileUploader() {
  const { upload, progress, isUploading, data, error, reset } = useFileUpload();

  return (
    <div>
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) upload(file, { contentType: file.type, filename: file.name });
        }}
      />
      {isUploading && <ProgressBar value={progress} />}
      {data && <p>Uploaded: {data.url}</p>}
      {error && <p className="error">{error.message}</p>}
      {data && <button onClick={reset}>Upload Another</button>}
    </div>
  );
}

useRest

Returns the REST module directly for imperative REST API calls.

const rest = useRest();

// rest.get<T>(path, options?)
// rest.post<T>(path, body?, options?)
// rest.put<T>(path, body?, options?)
// rest.patch<T>(path, body?, options?)
// rest.delete<T>(path, options?)

Example:

function ProductActions() {
  const rest = useRest();

  const handleExport = async () => {
    const csv = await rest.get<string>('/api/products/export');
    downloadCsv(csv);
  };

  return <button onClick={handleExport}>Export CSV</button>;
}

useRestQuery

Declarative hook for GET requests to custom REST endpoints.

const { data, error, isLoading, refetch } = useRestQuery<T>(path, options?);

Options extend RestRequestOptions ({ headers?, params?, signal? }) with enabled?: boolean.

Return:

| Field | Type | Description | | ----------- | --------------------- | ------------------------ | | data | T \| null | Response data | | error | ArchieError \| null | Error if failed | | isLoading | boolean | True on initial load | | refetch | () => Promise<void> | Manually trigger refetch |

Example:

const { data, isLoading } = useRestQuery<Product[]>('/api/products');
const { data: product } = useRestQuery<Product>(`/api/products/${id}`, { enabled: !!id });

useRestMutation

Imperative hook for POST/PUT/PATCH/DELETE requests.

const { execute, data, error, isLoading, reset } = useRestMutation<T>(path, method?);

Params:

| Param | Type | Default | Description | | -------- | ---------------------------------------- | -------- | ------------------ | | path | string | — | REST endpoint path | | method | 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' | 'POST' | HTTP method |

Return:

| Field | Type | Description | | ----------- | --------------------------------------------------------------------- | -------------------------- | | execute | (body?, overrideOptions?: RestRequestOptions) => Promise<T \| null> | Execute the request | | data | T \| null | Last successful result | | error | ArchieError \| null | Last error | | isLoading | boolean | True while executing | | reset | () => void | Clear data/error/isLoading |

The execute function accepts an optional second parameter overrideOptions (RestRequestOptions) to pass custom headers, params, or signal per request.

Example:

const { execute, isLoading } = useRestMutation<Product>('/api/products', 'POST');

const handleCreate = async () => {
  const product = await execute({ name: 'Widget', price: 9.99 });
  if (product) router.push(`/products/${product.id}`);
};

// With custom headers per request
await execute(payload, { headers: { 'x-idempotency-key': uuid() } });

Components

AuthGuard

Conditional rendering based on authentication state.

<AuthGuard fallback={<LoginPage />} loadingComponent={<Spinner />} requiredRoles={['admin']}>
  <ProtectedContent />
</AuthGuard>

Props:

| Prop | Type | Default | Description | | ------------------ | ----------- | ------- | ---------------------------------------------------- | | children | ReactNode | — | Content shown when authorized | | fallback | ReactNode | null | Content shown when NOT authenticated or unauthorized | | loadingComponent | ReactNode | null | Content shown while session loads | | requiredRoles | string[] | — | User must have at least one of these roles |

Behavior:

  1. While isLoading → render loadingComponent
  2. If not authenticated → render fallback
  3. If requiredRoles specified and user lacks all of them → render fallback
  4. Otherwise → render children

Nested guards:

<AuthGuard fallback={<LoginPage />}>
  <AppLayout>
    <AuthGuard requiredRoles={['admin']} fallback={<Unauthorized />}>
      <AdminPanel />
    </AuthGuard>
  </AppLayout>
</AuthGuard>

Re-exported Types

For convenience, @archie/react-sdk re-exports all essential types from @archie/js-sdk, so you don't need to import from both packages:

import {
  // Client
  createClient,
  type ArchieClient,
  type ArchieClientOptions,

  // Auth types
  type Session,
  type User,
  type AuthSignUpParams,
  type AuthSignInParams,
  type AuthConfirmParams,
  type AuthRecoverParams,
  type AuthResetPasswordParams,
  type AuthEvent,
  type AuthEventCallback,

  // GraphQL types
  type GraphQLResponse,
  type GraphQLRequestOptions,
  type GraphQLRawRequest,

  // File types
  type FileUploadOptions,
  type FileUploadResult,
  type CsvUploadOptions,

  // Realtime types
  type Subscription,
  type SubscriptionCallbacks,
  type RealtimeEvent,
  type RealtimeChannel,
  type ConnectionState,
  type ConnectionStateCallback,

  // REST types
  type RestRequestOptions,

  // Error classes
  ArchieError,
  AuthError,
  GraphQLError,
  NetworkError,

  // Storage
  type StorageAdapter,
  BrowserLocalStorage,
  MemoryStorage,

  // Interfaces (SOLID)
  type IAuthModule,
  type IGraphQLModule,
  type IFileModule,
  type IRealtimeModule,
  type IRestModule,
  type IHttpClient,
  type Logger,
  type TokenAccessor,
} from '@archie/react-sdk';

// React-specific prop types
import type { ArchieProviderProps, AuthGuardProps } from '@archie/react-sdk';

Error Handling

All useAuth methods return { data/session, error } — they never throw. For useQuery and useMutation, errors are exposed via the error field in the return object.

Error classes from @archie/js-sdk:

| Class | When | | -------------- | --------------------------------------------------------- | | AuthError | Authentication failures (sign in, sign up, token refresh) | | GraphQLError | GraphQL response errors | | NetworkError | Network failures, timeouts | | ArchieError | Base class for all Archie errors |

const { error } = useQuery<{ users: User[] }>('{ users { id } }');

if (error) {
  if (error instanceof AuthError) {
    // Token expired, redirect to login
  } else if (error instanceof NetworkError) {
    // Show offline indicator
  } else {
    // Show generic error
  }
}

SSR Compatibility

All hooks are SSR safe:

  • No window or document access on initial render
  • refetchOnWindowFocus checks typeof window before adding listeners
  • ArchieProvider initializes session asynchronously (no blocking SSR)
  • AuthGuard renders loadingComponent during server render when session is unknown

For frameworks like Next.js, create the client outside the component tree:

// lib/archie.ts
import { createClient } from '@archie/js-sdk';

export const archie = createClient({
  projectId: process.env.NEXT_PUBLIC_ARCHIE_PROJECT_ID!,
  apiKey: process.env.NEXT_PUBLIC_ARCHIE_ANON_KEY!,
});

// app/providers.tsx
('use client');
import { ArchieProvider } from '@archie/react-sdk';
import { archie } from '@/lib/archie';

export function Providers({ children }: { children: React.ReactNode }) {
  return <ArchieProvider client={archie}>{children}</ArchieProvider>;
}

Advanced Client Options

@archie/react-sdk inherits all advanced options from @archie/js-sdk via createClient(). These are configured once at client creation and propagate automatically to all hooks:

import { createClient, consoleLogger } from '@archie/js-sdk';

const archie = createClient({
  projectId: 'your-project-uuid',

  // Retry & timeout
  retries: 3, // retry transient errors (429, 500, 502, 503, 504, network)
  retryDelay: 200, // initial backoff delay in ms (exponential with jitter)
  timeout: 15_000, // abort requests after 15 seconds (0 = disabled)

  // Custom fetch — for testing, proxies, or edge runtimes
  fetch: customFetchImpl,

  // Logger
  logger: consoleLogger, // or a custom Logger implementation
});

| Option | Type | Default | Description | | ------------ | -------------- | ------------------ | --------------------------------------- | | timeout | number | 30000 | Request timeout in ms (0 = disabled) | | retries | number | 0 | Max retry attempts for transient errors | | retryDelay | number | 200 | Initial backoff delay in ms | | fetch | typeof fetch | globalThis.fetch | Custom fetch implementation |

Health Check

The underlying client also exposes ping():

const archie = useArchieClient();

const isUp = await archie.ping();

See the full @archie/js-sdk README for detailed documentation on retry behavior, backoff strategy, timeout semantics, and custom fetch usage.


TypeScript

All hooks are fully generic:

interface User {
  id: string;
  email: string;
  name: string;
}

// Typed query response
const { data } = useQuery<{ users: User[] }>('{ users { id email name } }');
//     ^? { users: User[] } | null

// Typed mutation response
const { mutateAsync } = useMutation<{ createUser: User }>('mutation ...');
const result = await mutateAsync({ input: { email: '[email protected]' } });
//    ^? { createUser: User } | null

// Typed subscription
const { data } = useSubscription<{ orderUpdated: Order }>('subscription ...');
//     ^? { orderUpdated: Order } | null

// Typed REST
const { data } = useRestQuery<Product[]>('/api/products');
//     ^? Product[] | null

License

MIT