What are React Signals? Why and when to use it?

Everything you need to know about React Signals. When to use React Signals and how to use it. Complete how-to on React Signals.

What are React Signals? Why and when to use it?
Varun Raj

Varun Raj

Product

When ever we pick frontend framework, the primary requirement in the modern era is the state management of the framework and the ability to update the changes to the UI without additional efforts. ReactJS brought a concept of Virtual DOM by which the components are re-rendered whenever it's state is updated. This sounds pretty neat as we have very less work in building a fluid UI for our apps.

This updation works on the basis of diffing, so whenever a state change happens the react library will find a diff by comparing the changes and updates the UI accordingly. This means a lot of computation happening in the backend even if the UI wouldn't have to update. In small apps this would work fine, but as your app grows you can feel the difference when the components pass on props in a lot of depth, every change in the parent makes the child as well re-render.

What are Signals?

Signals are a new concept in the frontend world where these are very much similar to our state primitives but works in a smarter way such that it updates only the components that uses the signal values rather than all the components it being passed.

Signals have been initially introduced in the SolidJS framework but slowly evolved to several other frameworks including ReactJS with @preact/signals-react package.

In this article I will compare React's State and Signal side by side to make it super clear for when to and why to use signals.

State vs Signal

I've created a very simple counter component example with both state and signal.

State Implementation

import React, { useState } from 'react'
 
// A todo list component
export default function StateCounterComponent() {
  const [count, setCount] = useState(0)
 
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}

Signal Implementation

import React from 'react'
import { useSignal } from '@preact/signals-react'
 
export default function SignalCounterComponent() {
  const count = useSignal(0)
 
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => count.value++}>Click me</button>
    </div>
  )
}

If you noticed, its pretty much same except the way I created the count variable. But when you run the component and check the re-rendering you'll clearly notice the difference.

![[counter-example.mp4]]

So when every I hit the button to increase the counter the entire component is re-rendered since the state update happens.

While in the case of signal, you see nothing but still the value changes. The reason for this is that the signals can update the value in the render function at a very granular level so that the component doesn't have to re-render every time, this sounds magical but it is true.

I've created an another example to show the difference, this time a traditional todo list, the Parent component contains the list of todos which is passed to a list component without being used in the parent. The list component then loops and renders them.

State Implementation

import React, { useState } from 'react'
 
const TodoListItem = ({ todo }) => {
  const [checked, setChecked] = useState(todo.checked)
  return (
    <li>
      <input
        type='checkbox'
        checked={checked}
        onChange={(event) => {
          setChecked(event.target.checked)
        }}
      />
      &nbsp;{todo.title}
    </li>
  )
}
const TodoList = ({ todos }) => {
  return (
    <ul className='p-2 text-left'>
      {todos.map((todo, index) => (
        <TodoListItem key={index} todo={todo} />
      ))}
    </ul>
  )
}
 
export default function SignalTodoListComponent() {
  const [todos, setTodos] = useState([])
  return (
    <div>
      <h2>Todo List</h2>
      <TodoList todos={todos} />
      <input
        type='text'
        onKeyDown={(event) => {
          if (event.key === 'Enter') {
            setTodos([
              ...todos,
              {
                id: todos.length,
                title: event.target.value,
                completed: false
              }
            ])
            event.target.value = ''
          }
        }}
      />
      <button>Add</button>
    </div>
  )
}

Signal Component

import React from 'react'
 
import { useSignal } from '@preact/signals-react'
 
const TodoListItem = ({ todo }) => {
  const checked = useSignal(todo.checked)
  return (
    <li>
      <input
        type='checkbox'
        checked={checked.value}
        onChange={(event) => {
          checked.value = event.target.checked
        }}
      />
      &nbsp;{todo.title}
    </li>
  )
}
const TodoList = ({ todos }) => {
  return (
    <ul className='p-2 text-left'>
      {todos.value.map((todo, index) => (
        <TodoListItem key={index} todo={todo} />
      ))}
    </ul>
  )
}
 
export default function SignalTodoListComponent() {
  const todos = useSignal([])
  return (
    <div>
      <h2>Todo List</h2>
      <TodoList todos={todos} />
      <input
        type='text'
        onKeyDown={(event) => {
          if (event.key === 'Enter') {
            todos.value = [
              ...todos.value,
              {
                id: todos.value.length,
                title: event.target.value,
                completed: false
              }
            ]
            event.target.value = ''
          }
        }}
      />
      <button>Add</button>
    </div>
  )
}

And when you add new items to the list you can see the entire parent gets updated and re-re-rendered even if the todo array is not actively used in the parent in state implementation, while in the signal implementation only TodoList component is re-rendered as we're using the array to loopover.

![[todolist-example.mp4]]

I hope this helps you understand how a simple change to signal from state can help you avoid unnecessary re-renders in your component tree and helps in building more fluid apps.

We're hoping to see the react's own implementation of signal, but you can start using Preact's package as well in your apps.

Last updated: February 28th, 2024 at 10:08:54 AM GMT+0

Subscribe

Get notified about new updates on Product Management, Building SaaS, and more.

Get things started. Now.

You have a business problem that you want to get off your mind? Need a trusted company to get it done for you and just show up with the solution? Talk to Skcript.