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.
Problem
When porting components from Preact (Deco Fresh) to React (TanStack Start), module-level signals from
@decocms/blocks/sdk/signalbehave differently:signal.valuein render automatically subscribes the component and triggers re-renders on change (Preact signals magic).signal.valuein 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
Writing to signals (
signal.value = x) is safe anywhere — only reading in render requiresuseSignalValue.Notes
useSignal(v)(component-scoped) is backed byuseStateand is safe to read in render within the same component.signal()inuseUI.tsor similar) needuseSignalValuewhen read in render.Affected files in a typical TanStack Start migration
useUI()to control visibility (open/close state).Related
useSignalValueis available from~/sdk/signal— re-exported from@decocms/blocks/sdk/signal.