Skip to content

useProjectConfig

useProjectConfig reads a value from the remote config and subscribes to changes at that path. When the value changes on the server and the SDK re-fetches, the component re-renders automatically.

Must be called inside a component that is a descendant of SkyStateProvider. Throws an error if called outside a provider.


Signatures

typescript
// With fallback — data is T (never undefined)
function useProjectConfig<T>(
  path: string,
  fallback: T,
): { data: T; isLoading: boolean; error: Error | null }

// Without fallback — data is T | undefined
function useProjectConfig<T = unknown>(
  path?: string,
): { data: T | undefined; isLoading: boolean; error: Error | null }

Return Value

typescript
{
  data: T;           // Current value (or fallback if key is missing)
  isLoading: boolean; // true until the first fetch (success or failure) completes
  error: Error | null; // Last error, or null if the most recent fetch succeeded
}

data

The current value at path in the config, cast to T. If the key does not exist and a fallback was provided, data returns the fallback value. If the key does not exist and no fallback was provided, data is undefined.

The value is updated automatically when the server config changes and the SDK re-fetches.

isLoading

true from the initial render until the first fetch (successful or failed) completes. Use this to avoid showing stale or default UI before the server config is known.

After the first fetch, isLoading is always false, even during subsequent background re-fetches.

error

The most recent error from the HTTP client, or null if the last fetch succeeded. Errors are instances of SkyStateError or standard Error.

The component continues to render with the last known value (or fallback) when an error occurs — errors do not clear the cache.


Usage Examples

Boolean feature flag

tsx
import { useProjectConfig } from '@skystate/react';

export function NewCheckoutFlow() {
  const { data: enabled, isLoading } = useProjectConfig<boolean>(
    'features.newCheckout',
    false,
  );

  if (isLoading) return null;
  return enabled ? <NewCheckout /> : <LegacyCheckout />;
}

String value (UI copy)

tsx
export function AnnouncementBanner() {
  const { data: enabled } = useProjectConfig<boolean>('banner.enabled', true);
  const { data: text } = useProjectConfig<string>('banner.text', 'Welcome!');
  const { data: color } = useProjectConfig<string>('banner.color', '#3b82f6');

  if (!enabled) return null;

  return (
    <div style={{ backgroundColor: color, color: '#fff' }}>
      {text}
    </div>
  );
}

Number value (limit)

tsx
export function WidgetList() {
  const { data: maxItems } = useProjectConfig<number>('features.maxItems', 5);

  return (
    <ul>
      {ALL_WIDGETS.slice(0, maxItems).map(widget => (
        <li key={widget.id}>{widget.name}</li>
      ))}
    </ul>
  );
}

Maintenance mode overlay

tsx
export function App() {
  const { data: maintenanceEnabled } = useProjectConfig<boolean>(
    'maintenance.enabled',
    false,
  );

  return (
    <>
      {maintenanceEnabled && <MaintenanceOverlay />}
      <main>...</main>
    </>
  );
}

Reading the entire config object

Pass an empty string (or no path) to read the entire config as an object:

tsx
// Read entire config
const { data: allConfig } = useProjectConfig<Record<string, unknown>>();

// Equivalent with explicit empty string
const { data: allConfig } = useProjectConfig<Record<string, unknown>>('');

Handling loading and errors explicitly

tsx
export function PricingBadge() {
  const { data: badge, isLoading, error } = useProjectConfig<string>(
    'pricing.badge',
    'Popular',
  );

  if (isLoading) return <Skeleton />;
  if (error) return null; // Hide badge on error, show nothing

  return <span className="badge">{badge}</span>;
}

Dot-path Notation

The path argument uses dots to navigate the config object hierarchy:

json
{
  "banner": {
    "enabled": true,
    "text": "Hello!"
  },
  "features": {
    "maxItems": 5
  }
}
pathdata
'banner.enabled'true
'banner.text''Hello!'
'features.maxItems'5
'banner'{ enabled: true, text: 'Hello!' }
'' or no argumentThe entire config object

Auto-registration in Development

When all of the following are true, the SDK automatically creates a missing key with the fallback value:

  • The resolved environment is 'development'
  • A devKey is present (set via SKYSTATE_DEV_KEY)
  • A fallback was provided to useProjectConfig
  • The key does not exist in the current config
  • The initial load has completed
tsx
// In development: if 'banner.enabled' does not exist in SkyState,
// the SDK creates it with the value `true`.
const { data: enabled } = useProjectConfig<boolean>('banner.enabled', true);

In production (no devKey), the SDK logs a warning but returns the fallback normally.

See Auto-registration for full details.


TypeScript Notes

The generic parameter T is used to type data. The SDK does not perform runtime type checking — the cast is purely at the TypeScript level. If the server returns a value of a different type than expected, TypeScript will not catch it at runtime.

typescript
// TypeScript knows data is boolean (not boolean | undefined) because a fallback was provided
const { data } = useProjectConfig<boolean>('feature.enabled', false);
//     ^? boolean

// Without fallback, data is T | undefined
const { data } = useProjectConfig<boolean>('feature.enabled');
//     ^? boolean | undefined

For object-typed config values, use an interface:

typescript
interface BannerConfig {
  enabled: boolean;
  text: string;
  color: string;
}

const { data: banner } = useProjectConfig<BannerConfig>('banner', {
  enabled: true,
  text: 'Hello!',
  color: '#3b82f6',
});

Render Behaviour

Each call to useProjectConfig creates two subscriptions on the underlying ConfigStore:

  1. A subscription to path for the value itself.
  2. A subscription to '__status' for isLoading and error.

The component only re-renders when the value at path (or one of its descendants) changes, or when the loading/error status changes. Changes to unrelated config paths do not trigger a re-render.

Object references are stable when values have not changed. If the server returns a config where banner.text changed but features.maxItems did not, the features object has the same reference as before.

Built with VitePress