Skip to content

React migration: use useSignalValue for module-level signals in render #423

Description

@JonasJesus42

Problem

When porting components from Preact (Deco Fresh) to React (TanStack Start), module-level signals from @decocms/blocks/sdk/signal behave differently:

  • Preact: reading signal.value in render automatically subscribes the component and triggers re-renders on change (Preact signals magic).
  • React: reading signal.value in render does NOT subscribe the component. The value is read once and never updates — the component appears broken (e.g. drawers never open, modals stay closed).

Required pattern in React

// ❌ BROKEN in React — reads once, never re-renders on signal change
const { displayCart } = useUI();
return <Drawer open={displayCart.value} />;

// ✅ CORRECT — useSignalValue uses useSyncExternalStore under the hood
import { useSignalValue } from "~/sdk/signal";
const { displayCart } = useUI();
const displayCartValue = useSignalValue(displayCart);
return <Drawer open={displayCartValue} />;

Writing to signals (signal.value = x) is safe anywhere — only reading in render requires useSignalValue.

Notes

  • useSignal(v) (component-scoped) is backed by useState and is safe to read in render within the same component.
  • Only module-level signals (from signal() in useUI.ts or similar) need useSignalValue when read in render.
  • This pattern affects all UI state components: drawers, modals, toasts, search bars, etc.

Affected files in a typical TanStack Start migration

  • Any component that reads a shared signal from useUI() to control visibility (open/close state).
  • Common symptoms: drawer opens briefly and closes, modal never opens, CSS class never updates reactively.

Related

  • useSignalValue is available from ~/sdk/signal — re-exported from @decocms/blocks/sdk/signal.

Activity

  1. JonasJesus42 commented on Oct 7, 2026

    @JonasJesus42
    ContributorAuthor

    Fixed by #437: useSignalValue ships in the scaffolded signals shim and the signal-value-reads post-cleanup audit rule flags module-level .value reads in render. Guidance lives in decocms/migrations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions