Frontend engineers have a long history of overengineering the simplest concept in the world: state.
We’ve built entire libraries to manage it, written reducers and selectors, passed it down through deep prop drilling chains, or wrapped entire trees in context providers. Sometimes, that complexity is justified. But more often, it’s just inertia: "that’s how we do state."
What if, in the majority of simpler scenarios, we could manage reactive state with a fraction of the ceremony? Preact’s signal offers exactly that. It’s a small primitive that invites you to rethink how you manage state with a simpler, more direct approach.
What Is a Signal?
In Preact, a signal is a reactive value that updates the UI when it changes without triggering a full component re-render.
Here’s the idea:
import { signal } from '@preact/signals'
const count = signal(0)
// Read
console.log(count.value) // 0
// Update
count.value++
That's it. No hooks. No useState, useReducer, or context. It works outside of components and is incredibly lightweight.
It’s worth noting that a signal is not a primitive; it's a wrapper object. You’ll need to access its .value property when using it in logic (like conditionals or computations). But in JSX, Preact automatically unwraps it for you:
// count.value is required in logic:
if (count.value > 5) {
console.log('High count')
}
// … but count works directly in JSX:
return <p>The count is {count}</p>
In fact, passing the signal itself (rather than its .value) into JSX is slightly more performant in Preact. It allows the framework to track dependencies more efficiently and avoid unnecessary re-renders.
Signals in Components
When you do use a signal inside a component, it’s seamless:
import { signal } from '@preact/signals'
const count = signal(0)
export function Counter() {
return <button onClick={() => count.value++}>Count is {count}</button>
}
You can drop the signal into JSX directly. The component will update automatically when the signal changes, but without rerunning the entire component function.
Why Signals Feel So Clean
Signals are powerful not because they do more—but because they do less:
- ✅ No rules of hooks to follow
- ✅ No unnecessary prop drilling
- ✅ No boilerplate for context
- ✅ No stale closures
- ✅ No render → effect → update loops
You're writing plain JavaScript variables that reactively update your UI. It feels honest, like the mental model matches the code.
State Where You Need It
With signals, you can colocate state wherever it makes sense:
- Global shared state? Export a signal.
- Component-level state? Use one directly.
- State that lives outside the component tree? Totally fine.
Because signal works outside of React-style component lifecycles, you're free to think about state as just a value—not something tied to hooks or render phases.
Signals Aren’t a Silver Bullet (and That’s Good)
Let’s be clear: signal doesn’t solve every problem. It’s by no means a replacement for full-blown state machines, transactional logic, or deeply nested undo functionality.
But for a surprisingly large portion of what frontend engineers deal with daily: toggle states, form values, selected tabs, UI feedback, etc., it’s a breath of fresh air.
Final Thought: Less State, More Simplicity
Preact’s signal is a reminder that the best tools don’t add layers, they remove them.
Although this post focuses on Preact, the signal pattern has gained traction across other frameworks, and even works standalone. It’s a lightweight concept with wide applicability, wherever you need reactivity without the bloat.
Uncomplicate your state. You’ll breathe easier, and so will your codebase.