React + Signals = Vue 3

Fotis Adamakis
5 min readJul 26, 2023

Signals are being adopted rapidly by all modern javascript frameworks. They have been around forever but resurfaced recently by Solidjs, and now every major javascript framework including Qwik, Preact, and Angular are adopting them. They can significantly reduce boilerplate but inevitably the implementation will look more or less the same no matter the underlying framework. Let's see an example featuring the two more popular frameworks right now, React and Vue.

Understanding the problem

Javascript is a very powerful language, but it's not well suited for reactive programming. To demonstrate the problem let's use a simple code snippet:

let a = 1
let b = 2

let c = a + b // 3

a += 1

console.log(c) // 3, but a + b is now 4

This is essentially the problem many reactive libraries are trying to solve.

Understanding Signals

Signals are a way to represent and manage state in a reactive way. They are essentially event-driven variables that can be subscribed to by other parts of the application. When the value of a signal changes, all of its subscribers are notified and their code will re-run. This allows for a very efficient way to update the UI in response to changes in state.

It is easy to understand with a small example written in SolidJs:

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

// Set the initial value of the signal
count = 10;

// Subscribe to the signal and print the current value
count.subscribe(value => console.log(value));

// Change the value of the signal
setCount(20);

Adoption in React

From useState to Signals

Currently, to manage state between components in react you need to pass it through props, create some context or use something like redux that comes with its own set of problems and boilerplate.

Preact, which is branded as a lightweight and fast version of React, is showing the way by implementing signals first.

At its core, a signal in Preact is an object with a .value property that holds the value. The value can change, but the signal itself always stays the same.

import { signal } from "@preact/signals";

const count = signal(0);

// Read a signal’s value by accessing .value:
console.log(count.value); // 0

// Update a signal’s value:
count.value += 1;

// The signal's value has changed:
console.log(count.value); // 1

In Preact, when a signal is passed down through a tree as props or context, we are only passing around references to the signal itself. This means that the signal can be updated without re-rendering any components that are not subscribed to the signal. This is because the components see the signal, not its value. As a result, Preact can skip the expensive rendering work and only re-render the components that actually need to be updated.

Signals have a second important characteristic: they track when their value is accessed and when it is updated. This means that when you access a signal’s .value from within a component, Preact will automatically re-render the component if the signal’s value has changed since the last time the component was rendered. This ensures that the UI always reflects the current state of the application.

import { signal } from "@preact/signals";

// Create a signal that can be subscribed to:
const count = signal(0);

function Counter() {
// Accessing .value in a component automatically re-renders when it changes:
const value = count.value;

const increment = () => {
// A signal is updated by assigning to the `.value` property:
count.value++;
}

return (
<div>
<p>Count: {value}</p>
<button onClick={increment}>click me</button>
</div>
);
}

Additionally, multiple signals can be combined using the computed function. The returned computed signal is read-only, and its value is automatically updated when any signals accessed from within the callback function change.

import { signal, computed } from "@preact/signals";

const todos = signal([
{ text: "Buy groceries", completed: true },
{ text: "Walk the dog", completed: false },
]);

// create a signal computed from other signals
const completed = computed(() => {
// When `todos` changes, this re-runs automatically:
return todos.value.filter(todo => todo.completed).length;
});

// Logs: 1, because one todo is marked as being completed
console.log(completed.value);

Everything above is written in Preact but is directly transferable to react using the package signals-react and using the same syntax.

// React component
import { signal } from "@preact/signals-react";

const count = signal(0);

function CounterValue() {
// Whenever the `count` signal is updated, we'll
// re-render this component automatically for you
return <p>Value: {count.value}</p>;
}

Signals in Vue

If you are a Vue developer but not familiar with Signals everything above should be easy to follow. The code looks almost identical to Vue 3.

Vue Developer Reading Signals in Preact

This is because reactivity is the fundamental idea behind Vue even since version 1. The same code in Vue would look like this:

import { reactive, computed } from "vue";

const todos = reactive([
{ text: "Buy groceries", completed: false },
{ text: "Walk the dog", completed: false },
]);

const completed = computed(() => {
return todos.filter(todo => todo.completed).length;
});

todos[0].completed = true

console.log(completed.value);

The code is almost identical to the code written in React using @preact/signals!

Conclusion

It’s apparent that the code looks the same. But is this a bad thing? Signals are definitely doing something well and this is why all the major frameworks are adopting them rapidly. But at the end of the day, maintainable code and good development ergonomics are what we want, and having all the frameworks using the same tools will create synergies that everyone will benefit from. Additionally having a skill transferable between frameworks and different codebases can only be beneficial to someone's career.

Additional Resources

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Written by Fotis Adamakis

« Senior Software Engineer · Author · International Speaker · Vue.js Athens Meetup Organizer »

Responses (3)

What are your thoughts?

I think this is why I stick with Vue 3. The reactive variables in Vue make it easy to write SPAs that are fast, small and optimized.

Neither signals nor any framework/library can help you if you don't know what you are doing and what is the lifecycle of your implementation.

Recently I rewrote an application from Vue to React. I replaced all computed properties by Reacts `useMemo`. It was a one-on-one replacement and it works as a charm.
```const tickerPriceActiveCurrency = useMemo(() => { return !tickerPrice ? 0 …...