Hooks

useDocyrusInventory

Shared app-utils inventory cache for Docyrus metadata (apps, data sources with fields, enums, views and forms, users, brands, preferences) plus a post-sign-in warm-up hook that drives a loading screen.

iOSAndroidExpo Go

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-inventory
Required Packages(2 packages)
pnpm add @docyrus/app-utils @docyrus/api-client

The module exports:

  • getSharedDocyrusInventory(client): returns the process-wide shared InventoryClient for a RestApiClient, creating it on first use. Instances are memoized in a WeakMap keyed by client. null in gives null out.
  • useDocyrusInventoryLoader(options): headless hook that runs inventory.load() once per client after sign-in and reports progress, so the app can show a loading screen before the home screen.

Requires @docyrus/app-utils >= 0.20.0 (ships a React Native build; no Metro config needed).

Why this exists

createInventoryClient from @docyrus/app-utils caches the metadata a Docyrus app needs at startup: apps, data sources (with embedded views, forms, fields and field enums), users, brands and tenant preferences. The data sources come from one GET /v1/apps/data-sources?expand=views,forms,fields request.

The cache lives inside the returned client, so everything must share one instance. Then a single warm-up fills the cache, and every inventory-backed Docyrus hook reads from it instead of fetching again on mount. getSharedDocyrusInventory hands out that instance; the Docyrus hooks call it internally.

A new client identity (for example after a tenant switch that rebuilds the client) gets a fresh inventory. If you reuse the same client across a tenant switch, call inventory.refresh().

getSharedDocyrusInventory(client)

import { createDataSourceClient } from '@docyrus/app-utils';

import { getSharedDocyrusInventory } from '@/hooks/docyrus-native/use-docyrus-inventory';

const inventory = getSharedDocyrusInventory(apiClient);
const dataSources = createDataSourceClient(apiClient, { inventory });

// Served from the shared cache (fields, enums, views and forms embedded)
const contacts = await dataSources.getBySlug('crm', 'contacts');
ParameterTypeDescription
clientRestApiClient | null | undefinedThe authenticated API client.

Returns: InventoryClient for a client, null when client is nullish. Overloaded so a non-null client returns a non-null InventoryClient type.

useDocyrusInventoryLoader(options)

Runs inventory.load() once as soon as enabled is true and a client exists. It is StrictMode-safe and re-runs automatically when the client identity changes. Failures do not throw: status becomes 'error' and reload() retries. The app still works on error, because inventory-backed reads fall back to the API.

import { ActivityIndicator, Text, View } from 'react-native';
import { useDocyrusAuth, useDocyrusClient } from '@docyrus/signin/react-native';

import { Button } from '@/components/docyrus-native/button';
import { Progress } from '@/components/docyrus-native/progress';
import { useDocyrusInventoryLoader } from '@/hooks/docyrus-native/use-docyrus-inventory';

const APP_ID = process.env.EXPO_PUBLIC_DOCYRUS_APP_ID;
// Keep array options stable (module scope or useMemo).
const CONFIG_APP_IDS = APP_ID ? [APP_ID] : [];

export function InventoryGate({ children }: { children: React.ReactNode }) {
  const { status } = useDocyrusAuth();
  const client = useDocyrusClient();
  const { status: loadStatus, progress, reload } = useDocyrusInventoryLoader({
    client,
    enabled: status === 'authenticated',
    configAppIds: CONFIG_APP_IDS
  });

  if (loadStatus === 'ready') return children;

  return (
    <View className="flex-1 items-center justify-center gap-4 p-6">
      {loadStatus === 'error' ? (
        <Button onPress={reload}>Retry</Button>
      ) : (
        <>
          <ActivityIndicator />
          <Text className="text-muted-foreground">{progress?.message ?? 'Loading…'}</Text>
          <Progress value={Math.round((progress?.ratio ?? 0) * 100)} />
        </>
      )}
    </View>
  );
}

Options (UseDocyrusInventoryLoaderOptions)

OptionTypeDefaultDescription
clientRestApiClient | null | undefined—The authenticated API client. The loader waits until it is set.
enabledbooleantrueGate the warm-up (e.g. only once authenticated). A non-null client is still required.
includeInventoryLoadStepId[]app-utils default: ['apps', 'data-sources', 'users', 'brands', 'preferences']Tenant-wide steps to warm. Drop 'users' when the user may lack the Users.Read.All scope.
configAppIdsstring[]app-utils defaultApp ids whose config and user-config to warm. app-utils defaults to the VITE_APP_ID env var, which does not exist under Metro, so pass ids explicitly on native. [] skips config steps.
forcebooleanapp-utils default: trueInvalidate caches before loading so the warm-up makes fresh network calls.

Result (DocyrusInventoryLoaderState)

FieldTypeDescription
inventoryInventoryClient | nullThe shared inventory for the current client.
status'idle' | 'loading' | 'ready' | 'error'Warm-up lifecycle.
progressInventoryLoadProgress | nullLatest progress event. progress.ratio (0 to 1) drives a determinate bar; progress.message is an English label.
errorunknownThe load error when status === 'error'.
resultInventoryLoadResult | nullThe loaded payload once ready.
isLoadingbooleantrue while warming.
isReadybooleantrue once warmed successfully.
reload() => voidRe-run the warm-up (Retry button, tenant switch).

Type Exports

TypeDescription
DocyrusInventoryLoadStatus'idle' | 'loading' | 'ready' | 'error'.
UseDocyrusInventoryLoaderOptionsOptions of useDocyrusInventoryLoader.
DocyrusInventoryLoaderStateReturn value of useDocyrusInventoryLoader.

On this page