Stacksheetv1.1.4

Type Registry

Pre-register sheet types for compile-time type checking.

Overview

By default, you pass components directly to actions like open(MyComponent, { props }). For larger apps where you want compile-time type checking on every action call, you can pre-register sheet types in a type map.

This is optional — most apps don't need it.

Setup

Define your sheet types as a generic parameter to createStacksheet(), then pass the component map to the provider:

import { createStacksheet } from "@howells/stacksheet";

const { StacksheetProvider, useSheet } = createStacksheet<{
  "user-profile": { userId: string };
  "settings": { tab?: string };
}>();

function UserProfile({ userId }: { userId: string }) {
  const { close } = useSheet();
  return (
    <div>
      User {userId}
      <button onClick={close}>Close</button>
    </div>
  );
}

function Settings({ tab }: { tab?: string }) {
  return <div>Settings: {tab}</div>;
}

function App() {
  return (
    <StacksheetProvider
      sheets={{
        "user-profile": UserProfile,
        "settings": Settings,
      }}
    >
      <YourApp />
    </StacksheetProvider>
  );
}

Type-checked actions

With the type map, actions accept a string key instead of a component reference. TypeScript enforces correct data shapes:

const { open } = useSheet();

// Correct
open("user-profile", "u1", { userId: "abc" });

// Type error: missing userId
open("user-profile", "u1", {});

// Type error: "unknown-type" is not a key
open("unknown-type", "x", {});

The sheets prop on the provider is also fully typed — TypeScript enforces that every key in your type map has a matching component with the correct props.

Mixing with direct components

Type-registry sheets and direct-component sheets can coexist in the same stack:

const { useSheet } = createStacksheet<{
  "user-profile": { userId: string };
}>();

const { open, push } = useSheet();

// Registry sheet (string key)
open("user-profile", "u1", { userId: "abc" });

// Direct component (same stack)
push(QuickActions, { items: ["edit", "delete"] });

Inline component warning

In development, Stacksheet warns if you pass an inline arrow function as a component:

// Warning: new component reference on every render
push(({ userId }) => <div>...</div>, { userId: "u_abc" });

This creates a new component on every render, which breaks React reconciliation. Define your components at module level or with React.memo instead.

When to use this

Consider the type registry when:

  • You have a fixed set of known sheet types used across many files
  • You want TypeScript to catch typos in sheet type names
  • You're using the store outside React and want typed open("key", ...) calls

For most use cases, passing components directly is simpler and equally type-safe (TypeScript infers the props from the component type).

On this page