signals

Guidelines for state management with createModel, Show, and For using Preact Signals.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "signals" with this command: npx skills add jovidecroock/skills/jovidecroock-skills-signals

Working with Signals

Guidelines for state management with createModel, Show, and For using @preact/signals and @preact/signals/utils.

What are Signals and Signal Utils?

Signals are reactive primitives from @preact/signals that provide fine-grained reactivity. @preact/signals/utils adds ergonomic helpers like Show and For for declarative rendering, while createModel comes from @preact/signals for model-driven state.

Basic Usage

Creating a Model with createModel

import { computed, createModel, signal } from '@preact/signals';

export const CounterModel = createModel(() => {
  const count = signal(0);
  const name = signal<string | null>(null);
  const items = signal<Item[]>([]);

  const hasItems = computed(() => items.value.length > 0);

  const increment = () => {
    count.value++;
  };

  const addItem = (item: Item) => {
    items.value = [...items.value, item];
  };

  return {
    count,
    name,
    items,
    hasItems,
    increment,
    addItem,
  };
});

Reading and Writing

import { useModel } from '@preact/signals';

const model = useModel(CounterModel);

// Read with .value
<span>{model.count.value}</span>

// Write with .value assignment
<button onClick={model.increment}>Increment</button>
<button onClick={() => (model.name.value = 'Alice')}>Set Name</button>

Declarative Rendering with Show and For

import { useModel } from '@preact/signals';
import { For, Show } from '@preact/signals/utils';

function ItemList() {
  const model = useModel(CounterModel);

  return (
    <>
      <Show when={model.hasItems} fallback={<p>No items yet.</p>}>
        <ul>
          <For each={model.items}>
            {(item) => <li key={item.value.id}>{item.value.label}</li>}
          </For>
        </ul>
      </Show>
    </>
  );
}

When to Use Signals

Use Signals For:

  1. UI state that changes frequently

    • Toggle states (open/closed, expanded/collapsed)
    • Form input values
    • Loading/error states
    • Progress indicators
  2. State used in event handlers

    • Drag/drop coordinates
    • Mouse position tracking
    • Resize dimensions
  3. State derived from async operations

    • Authentication status
    • API response data
    • Upload progress

Example Patterns from the Codebase

Toggle state (FAQ accordion model):

const FAQItemModel = createModel(() => {
  const open = signal(false);
  const toggle = () => {
    open.value = !open.value;
  };

  return { open, toggle };
});

function FAQItem({ question, answer }) {
  const model = useModel(FAQItemModel);

  return (
    <div>
      <button onClick={model.toggle}>
        {question}
      </button>
      <Show when={model.open}>
        <div>{answer}</div>
      </Show>
    </div>
  );
}

Auth state model:

const AuthModel = createModel(() => {
  const isAuthenticated = signal<boolean | null>(null);
  const checkingAuth = signal(true);

  const loadSession = async () => {
    const res = await authClient.getSession();
    isAuthenticated.value = !!res.data?.user;
    checkingAuth.value = false;
  };

  return { isAuthenticated, checkingAuth, loadSession };
});

const auth = useModel(AuthModel);
useEffect(() => {
  auth.loadSession();
}, []);

Upload progress:

const UploadModel = createModel(() => {
  const isDragging = signal(false);
  const isUploading = signal(false);
  const uploadError = signal<string | null>(null);
  const uploadProgress = signal(0);

  return { isDragging, isUploading, uploadError, uploadProgress };
});

Window drag state:

const WindowModel = createModel(() => {
  const isDragging = signal(false);
  const position = signal({ x: 0, y: 0 });
  const size = signal({ width: 800, height: 600 });

  return { isDragging, position, size };
});

Signals in Reusable Models

Encapsulate complex signal logic in models:

// models/WindowDragModel.ts
export const WindowDragModel = createModel(({ defaultWidth, defaultHeight }) => {
  const isDragging = signal(false);
  const position = signal({ x: 0, y: 0 });
  const size = signal({ width: defaultWidth, height: defaultHeight });

  // Event handlers that mutate signals...

  return {
    isDragging,
    position,
    size,
    handleMouseDown,
  };
});

// component
const drag = useModel(WindowDragModel);

Signals vs useState

Use CasePrefer
Simple boolean togglesignal in createModel
Object with multiple fields updated togethersignal in createModel
State updated in event handlerssignal in createModel
State passed deep into childrencreateModel + Show/For
State that rarely changesEither works
State managed by external libraryFollow library conventions

Best Practices

1. Type Your Signals

Always provide types for non-obvious signal values:

const user = signal<User | null>(null);
const status = signal<'idle' | 'loading' | 'error'>('idle');

2. Mutate Directly in Handlers

Signals don't need functional updates like useState:

// With signals - direct mutation is fine
onClick={() => count.value++}
onClick={() => items.value = [...items.value, newItem]}

// Reading current value in handler
onClick={() => {
  if (count.value < 10) {
    count.value++;
  }
}}

3. Use Show and For for Rendering

Prefer Show and For for common conditional and list rendering:

<Show when={isLoading}>
  <Spinner />
</Show>

<Show when={error}>
  <ErrorMessage>{error.value}</ErrorMessage>
</Show>

<For each={items}>
  {(item) => <Item key={item.value.id} {...item.value} />}
</For>

You can also use a callback in the when and each to simulate a computed signal if needed.

4. Combine with useEffect

Signals work alongside traditional hooks:

useEffect(() => {
  if (isOpen) {
    checkingAuth.value = true;
    fetchData().then(data => {
      result.value = data;
      checkingAuth.value = false;
    });
  }
}, [isOpen]);

5. Keep Signals Local When Possible

Prefer local signals over global state. Only lift state when multiple components need to share it.

Common Pitfalls

Don't Forget .value

// Wrong - won't update
{isOpen && <Modal />}

// Correct
{isOpen.value && <Modal />}

When using Show, pass a signal or computed signal to when:

<Show when={isOpen}>
  <Modal />
</Show>

Object Updates Need New References

// Wrong - won't trigger update
position.value.x = 100;

// Correct
position.value = { ...position.value, x: 100 };
// or
position.value = { x: 100, y: position.value.y };

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

nano-banana-2

Nano Banana 2 - Gemini 3.1 Flash Image Preview

Repository Source
General

qwen-image-2

Qwen-Image - Alibaba Image Generation

Repository Source
40.8K153inferen-sh
General

p-video

Pruna P-Video Generation

Repository Source
40.8K153inferen-sh
General

nano-banana

Nano Banana - Gemini Native Image Generation

Repository Source
40.8K153inferen-sh