Jagadhiswaran Devaraj

Mar 25, 2025 • 5 min read

React Signals Explained: High-Performance State Management with Preact Signals

Learn how Preact’s Signals bring fine-grained reactivity to React, replacing useState and useEffect with a faster, more efficient state management model.

If you’ve been building with React for a while, you’re probably familiar with the usual duo: useState and useEffect. They’ve been with us through thick and thin — handling everything from toggling modals to fetching data. But as your apps get bigger, more dynamic, and performance-sensitive, you start to feel the cracks. Too many re-renders, deeply nested state updates, and messy side effects make things harder than they should be.

That’s exactly where React Signals come into play — offering a new, more reactive way to handle state without the boilerplate, performance pitfalls, or re-render headaches.


What Exactly Are Signals?

Signal is like a reactive value. You can think of it as a container for data that automatically tracks who’s using it — and when it changes, only those parts get updated. No more guessing whether a useEffect dependency array is correct or worrying about unnecessary renders.

Signals were popularized by Preact — a lightweight React alternative — through the @preact/signals library. Preact’s signals model focuses on fine-grained reactivity and optimal performance. More recently, the concepts behind Preact Signals are being explored in React via the @preact/signals-react package, which acts as a compatibility layer. However, native support for Signals is not yet part of React core.

So when we talk about React Signals today, we’re referring to Preact’s implementation running inside a React environment via this package.

Preact signals have already proven to be extremely efficient in production environments. They use pull-based dependency tracking, meaning that when a component accesses a signal, it registers that access. When the signal updates, only the directly affected computation or DOM patch is re-run. No unnecessary component trees get re-rendered.

This granular tracking is at the core of Signals — and it's the reason why they're so powerful.


How Do They Work Under the Hood?

At a high level, Signals work like this:

  • You create a signal with an initial value.

  • Any component or computation that reads the signal gets automatically subscribed.

  • When the signal’s value changes, only those subscribers are notified.

Under the hood, signals use a reactive graph. Each signal maintains a list of subscribers (aka effects or components). When the signal’s value is changed via a setter, only the subscribers that depend on it are re-evaluated.

This means that your React component doesn’t have to re-render entirely. Instead, just the expressions or DOM elements that rely on the signal get patched. It’s more like updating a single part of the DOM, not the whole virtual DOM tree.

Contrast this with React’s typical rendering strategy, where updating state with useState or context triggers the component to re-render (and sometimes its children too).

Here’s a super simple example:

import { signal } from '@preact/signals-react';

const counter = signal(0);

function Counter() {
  return <p>Count: {counter.value}</p>;
}

function Button() {
  return <button onClick={() => counter.value++}>Increment</button>;
}

In this setup, only the part of the component that reads the signal (<p>) will update. Not the entire component. Not even the Button if it's part of the same component.

This is true fine-grained reactivity.


Why Use Signals?

  1. Fine-grained reactivity: Only parts of the UI that depend on the signal re-render or update.

  2. Cleaner logic: No need for useEffect just to sync values or watch for changes.

  3. Better performance: Reduces unnecessary re-renders, especially in large trees or deep component hierarchies.

  4. Simplified mental model: You read and write directly to signal.value. That’s it. No more juggling multiple hooks or memoization.

  5. No stale closures: Unlike useState, signal functions don’t suffer from closure-related bugs.

  6. Built-in tracking: Signals automatically track dependencies, so effects are only re-run when needed.


Replacing useState and useEffect

Let’s be honest — useEffect often ends up doing too much. Tracking state, syncing DOM, fetching data, debouncing inputs... it’s everywhere.

Signals aim to reduce that need. Instead of manually wiring up effects to listen to state changes, you simply read from a signal, and it just works.

Compare this:

const [name, setName] = useState('');

useEffect(() => {
  localStorage.setItem('name', name);
}, [name]);

With Signals:

const name = signal('');

effect(() => {
  localStorage.setItem('name', name.value);
});

No dependency arrays, no surprises. effect() automatically tracks the reactive reads inside it and re-runs only when name.value changes.


Understanding effect() and computed()

Two core utilities that work hand-in-hand with signals are effect() and computed().

  • effect(): Runs a function whenever any signal it reads changes. It’s similar to useEffect, but smarter — it automatically tracks dependencies based on usage.

effect(() => {
  console.log("The counter is", counter.value);
});

No dependency array required — the effect re-runs whenever counter.value changes.

  • computed(): Creates a derived signal based on other signals. It’s a read-only signal that recalculates only when dependencies change.

const doubled = computed(() => counter.value * 2);

This doubled signal updates only when counter.value changes, and it can be used in components just like a regular signal.

These utilities allow you to build reactive logic that’s easy to follow and performant by default.


Real-World Performance Benchmarks

In benchmarks comparing @preact/signals to React’s default state management:

  • Signals showed up to 10x faster updates in UI-heavy apps.

  • Memory usage dropped significantly, especially in apps with large trees and frequent updates.

  • Component update count was drastically reduced, with fewer full re-renders and more direct DOM patching.

In real-world apps like dashboards, collaborative editors, or animation-heavy interfaces, these performance wins make a noticeable difference.


Where Are Signals Useful?

  • Real-time dashboards or data-heavy UIs

  • Apps with complex, interdependent state logic

  • Live previews or text editors

  • Dynamic forms with dependent inputs

  • Scenarios where you want highly performant updates with minimal re-renders


Signals vs Context and Memoization

Signals can also reduce the need for React Context or overuse of useMemouseCallback, and React.memo. Because updates are localized and only affect what really needs updating, you don't have to wrap everything in memoization hacks.

For global or shared state, you can simply export and use signals directly. No provider trees required:

// signals/store.ts
export const theme = signal<'light' | 'dark'>('light');

// Any component
import { theme } from './signals/store';

function ToggleTheme() {
  return <button onClick={() => theme.value = theme.value === 'light' ? 'dark' : 'light'}>Toggle</button>;
}

No need for context providers. Just import and go.


Final Thoughts

Signals offer a refreshing approach to state in React. They bring us closer to true reactivity, with less boilerplate and better performance. Originally implemented by Preact, and now crossing over into the React ecosystem via compatibility layers, Signals represent a fundamental shift in how we think about state and reactivity.

If you’re tired of fighting useEffect, or just want a more efficient way to build, it’s worth keeping an eye on Signals. React is evolving — and with Signals, it’s getting a lot smarter.

Bonus: Signals are already being explored in libraries like @preact/signals@preact/signals-react, and reactivity. Dive in and start experimenting!

- Jagadhiswaran Devaraj


📢 Stay Connected & Dive Deep into Tech!

🚀 Follow me for hardcore technical insights on JavaScript, Full-Stack Development, AI, and Scaling Systems:

🐦 X (Twitter): jags

✍️ Read more on Medium: https://medium.com/@jwaran78

💼 Connect with me on LinkedIn: https://www.linkedin.com/in/jagadhiswaran-devaraj/

Let’s geek out over code, architecture, and all things in tech! 💡🔥

Join Jagadhiswaran on Peerlist!

Join amazing folks like Jagadhiswaran and thousands of other people in tech.

Create Profile

Join with Jagadhiswaran’s personal invite link.

3

5

1