Explore Blogs

Why signals are making react look old — But should you switch?

bpi-react-and-signal-libraries

Frontend frameworks are in the middle of a quiet revolution — where fine-grained reactivity and compiler-first mental models are challenging React's dominance. Signals are at the heart of this shift. They promise lightning-fast updates, less memory churn, and fewer re-renders — so much so that React suddenly feels... clunky.

The frontend landscape is shifting. For years, React's model of component re-renders and virtual DOM reconciliation has been the gold standard. But a new wave of frameworks — SolidJS, Qwik, Preact Signals — are changing the game by eliminating unnecessary re-renders altogether. At the heart of this movement is a primitive called a Signal, a low-level reactive value that updates the UI with surgical precision. The result? Apps that feel more like native software: fluid, responsive, and far easier to optimize. But are signals just a performance trick — or a fundamentally better way to write UI?

Let's unpack what makes signals so powerful, how they differ from React's mental model, and whether it's worth considering them for your next project.

A shifting mental model: From Virtual DOM to Reactive Primitives

For over a decade, React's model of declarative components and the virtual DOM has shaped how we build UIs. It introduced the idea that re-rendering everything is okay — as long as it's fast. But now, Signals are quietly challenging that premise.

In React:

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Each click re-renders the entire Counter component.

In SolidJS:

import { createSignal } from 'solid-js';

function Counter() {
  const [count, setCount] = createSignal(0);

  return (
    <div>
      <p>Count: {count()}</p>
      <button onClick={() => setCount(count() + 1)}>Increment</button>
    </div>
  );
}

Here, only the DOM node displaying count() is updated — not the function or surrounding JSX.

This reflects a fundamental shift:

  • React: Re-renders the full component tree (or parts of it).
  • Signals: Track dependencies and only re-run what truly depends on changed state.

How signals work: Fine-grained reactivity explained

Signals are reactive primitives that carry two core capabilities:

  • Dependency tracking
  • Granular updates

When you read a signal in your UI, it registers a dependency. When that signal changes, only those dependent parts re-execute.

Example with chained signals:

const price = createSignal(10);
const quantity = createSignal(2);
const total = () => price() * quantity();

console.log(total()); // 20
quantity(3);
console.log(total()); // 30

The total function automatically tracks price and quantity. Change either, and the dependent parts of the app update automatically.

Internally:

  • Signals store a list of observers.
  • Updates propagate precisely, like nodes in a dependency graph.
  • You avoid useEffect, useMemo, and the overhead of re-rendering components.

This model makes large applications easier to scale because every update touches the minimum amount of code needed to reflect a change.

Performance implications: Scaling the UI without reconciliation

Let's consider a dynamic dashboard with hundreds of widgets:

React Version:

  • Changing a single prop might cause entire sections to re-render.
  • Developers manually optimize using React.memo, useCallback, and useMemo.

Signals Version (SolidJS or Qwik):

  • Only the directly affected widgets update.
  • No VDOM diffing, no manual memoization needed.

Example: Real-time stock tracker

// React
function Stock({ symbol, price }) {
  return (
    <div>
      {symbol}: ${price}
    </div>
  );
}

function App({ data }) {
  return data.map((stock) => (
    <Stock
      key={stock.symbol}
      {...stock}
    />
  ));
}

This works — but React re-renders App and every Stock component on new data unless memoized.

Signals version:

const createStockSignal = (symbol, price) => ({
  symbol,
  price: createSignal(price),
});

function Stock({ stock }) {
  return (
    <div>
      {stock.symbol}: ${stock.price()}
    </div>
  );
}

function App({ stocks }) {
  return stocks.map((s) => <Stock stock={s} />);
}

Only the signal that changes triggers an update. This allows real-time performance without custom hooks or wrappers.

Now imagine that stock price updates are coming in every 500ms. In React, even with memo, the component tree has to be re-evaluated. With signals, only the DOM node with a changed value is touched.

Developer experience: Simpler or stranger?

Signals reduce abstraction layers. There's no hook rules, no dependency arrays, no need for useEffect hacks.

React fetch pattern:

useEffect(() => {
  fetchData().then(setData);
}, []);

Signals equivalent:

const [data] = createResource(fetchData);

Signals are more declarative. But developers used to hook-based lifecycles might need to re-learn how side effects and async flow work.

Here's a complex pattern solved more cleanly with signals:

const userId = createSignal(123);
const [userData] = createResource(userId, fetchUserData);

// Change the user ID and watch data update reactively:
userId(456);

This is a reactive pattern React struggles with, especially when dependency arrays or stale closures are involved.

Tooling is improving (e.g., Solid DevTools), but React still leads in this area.

Signals in the wild

  • SolidJS: Built entirely around signals.
  • Qwik: Pairs signals with resumable hydration.
  • Vue 3: Its Composition API mirrors signals with ref() and reactive().
  • Preact Signals: Drop-in reactivity layer for React, enabling local performance boosts.

Frameworks are converging on this idea: precise, signal-driven updates scale better.

Even React-compatible tooling like @preact/signals-react allows you to try out signals incrementally inside existing React apps.

Signals in React via @preact/signals-react

The @preact/signals-react library introduces signals to React, enabling components to automatically re-render when the signal's value changes. Here's how you can use it:

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

const count = signal(0);

function Counter() {
  return (
    <div>
      <p>Count: {count.value}</p>
      <button onClick={() => count.value++}>Increment</button>
    </div>
  );
}

In this example, updating count.value triggers a re-render of the Counter component, reflecting the new count without manual state management.​

React's response

The React team is exploring compiler-enhanced reactivity (React Compiler) and pushing boundaries with React Server Components.

But today:

  • React still relies on coarse component-level rendering.
  • Signals require a different architecture React isn't built for — yet.

React's Server Components and streaming architecture may eventually offer comparable benefits, but not with the same granularity of signals.

Expect React to evolve, but not overhaul its core just yet.

React Compiler — The future

With the React Compiler, you might be able to write this:

let count = 0;

function Counter() {
  return (
    <button onClick={() => count++}>
      Count is {count}
    </button>
  );
}

This would automatically track and update UI when count changes — no need for useState.

Sounds wild, right?

But React Compiler would:

  • Track variable usage across renders
  • Automatically trigger re-renders when a value changes
  • Optimize reactivity without manual hooks

This is similar to how frameworks like Solid.js or Svelte work — no explicit useState, but the reactivity still works.

Should you switch to Signals or wait for the React Compiler?

With the evolving landscape of React, you might be wondering whether to embrace the signal-based state management libraries (like @preact/signals-react) or wait for the React Compiler to transform the way we handle state in React apps. Let's break this down:

Signal libraries: A shift in React's Reactivity

Signals bring a fine-grained reactivity model that's more efficient than React's traditional useState or useEffect. Libraries like @preact/signals-react allow for components to automatically re-render when a signal's value changes — without the need for manual state management or hooks. This approach is inspired by frameworks like Solid.js and Qwik, known for their performance-focused, fine-grained reactivity.

  • Pros:
    • Highly performant — only re-renders dependent components.
    • Potentially reduces boilerplate code.
    • More intuitive state management in reactive applications.
  • Cons:
    • Still not native to React (third-party libraries).
    • Introduces a different mental model (which may require learning and adapting).

React Compiler: Automatic state optimization at build-time

The React Compiler is under active development and promises to significantly optimize performance at build-time. It will likely eliminate the need for certain patterns like useMemo or useCallback, and it might even replace useState in many cases with smarter auto-optimized state management.

In the future, you might be able to rely more on React's internal optimizations rather than manually using state hooks. The compiler could analyze your components at build time and automatically optimize them for better performance — potentially removing the need for hooks like useState in certain cases. This might make React more like Svelte or Solid, where state management is implicit and doesn't require hooks.

  • Pros:
    • Automatic performance optimizations.
    • Potentially simpler code with less boilerplate.
    • Integration with Concurrent React for seamless rendering improvements.
  • Cons:
    • Still evolving — may not be production-ready in the near future.
    • You may need to transition and adapt once it's fully released.

Should you switch?

Here's the big question: Should you start using signals today, or wait for the React Compiler to arrive?

  • If you're working on new projects or experimenting with cutting-edge features, trying out signal-based libraries might be worth it to understand the future direction of reactivity in React. This will give you a head start on what the React team is working towards and can help you explore fine-grained reactivity in a real-world context.
  • If you're working with existing projects or projects where stability and broad ecosystem support are important, it's better to stick with Concurrent React (i.e., using startTransition, Suspense, useState, useEffect, etc.) for now. The React Compiler is still being refined, and while it's an exciting future prospect, it's not yet stable or widely adopted.
  • Look at it as an evolution rather than a revolution. React's ecosystem is growing, and both signal-based state management and the React Compiler will likely coexist with hooks like useState in the near future. It's not an either/or choice — rather, a matter of adapting to new tools and paradigms as they become production-ready.

Conclusion: Keep learning, keep experimenting

For now, don't abandon hooks like useState. It's the foundation of React's state management, and it's not going anywhere. But be mindful of how signals and the React Compiler are evolving. These tools will enhance your ability to write highly performant React apps in the future, and it's worth experimenting with them as they become more mature.

Ultimately, whether you choose signals, continue with useState, or adopt the React Compiler, the key is to stay adaptable and embrace the new tools that fit your project needs. React's ecosystem is evolving rapidly, and you'll have the tools to leverage both current and future innovations effectively.

Signals are changing the frontend conversation — but they're not a silver bullet or a guaranteed React replacement. While frameworks like Solid and Preact Signals are showcasing incredible performance gains, React continues to evolve in powerful ways — with Server Components, Concurrent rendering, and an upcoming compiler. If you're shipping today, React remains a rock-solid choice. But staying curious about signals could put you ahead of the curve tomorrow.

Stay Updated

This site is protected by reCAPTCHA and the GooglePrivacy Policy andTerms of Service apply.