Fine-Tuning Your Reactivity
Harnessing the Power of Signals
🚦
What is reactivity?
What is reactivity?
There are so many definitions for reactivity, but I think the simplest one is
Declaratively express the relationship between values that change over time.
Ryan Carniato
C = A + B
A reactive system will make sure this expression is always up to date
What is reactivity?
Coarse-grain reactivity
Change in data would trigger a large amount of JavaScript to execute.
This meant that fast-changing properties such as animation would cause performance issues.
The framework would eventually reconcile all of the changes into the UI.
< 17
without vapor
State changes only update the part of the UI to which the state is bound to.
Fine-grained reactivity
The hard part is knowing how to listen to property changes in a way that has good DX.
>=17
with vapor
What defines a reactive system?
What defines a reactive system?
Reactive unit (wrapper, holder, subject, signal, etc)
Reactions (effect, watcher, autotun, subscription, etc)
Derivations (memo, computed, thunk, etc)
Scheduler (Concurrent updates, tasks, etc)
Dependency tracking (Automatic, Manual)
Algorithm (Push, Pull, Hybrid, Lazy, Eager, etc)
Other implementation details
Rutime
vs
Compile time
Rutime Reactivity
In JS we have two mainstream reactive primitives
Reactive Streams (RxJS) - a series/collection of data events that are emitted over time.
const a = 1;
const b = 2;
const c = a + b;import { map, combineLatest, BehaviorSubject } from 'rxjs';
// reactive wrappers, that always respond with latest value
const a$ = new BehaviorSubject(1);
const b$ = new BehaviorSubject(2);
// derived/computed state using operators
const c$ = combineLatest(a$, b$).pipe(map(([a, b]) => a + b));
// lazy
// effects, subscriptions are explicit
c$.subscribe((c) => console.log(c)); // 3
// push based
a$.next(2);
Observables/Subjects
Rutime Reactivity
Signals
const a = 1;
const b = 2;
const c = a + b;Signals are fundamentally a simple pub/sub system that holds a value and represents that value over time.
Rutime Reactivity
import { signal, computed, effect } from 'some-signals-library';
const a = signal(1)
const b = signal(2)
// dervied without memo
const c = () => a.value + b.value;
// or with memo
// depending on the reactivity alogorthim
// this could run immediately (eager) or on read
const cachedC = computed(() => a.value + b.value);
effect(() => console.log(c.value));
// update the wrapped value;
a.value += 1;Signals
Rutime Reactivity
Comp-time Reactivity
const a = 1
const b = 2
const c = a + b// .svelte
<script>
// it looks like js vars, but it is not
let a = 1
let b = 2
// computed/derived state, compiler will transform this to
// code that keeps this relation alive
$: c = a + b
b += 3
</script>
<p>{c}</p>
< v5
Comp-time Reactivity
Change detection is not always reactive

Signals-based reactive systems
Signals-based reactive systems

Simple
Composable
Declarative
Signals
under the hood

export function createSignal(value) {
const read = () => value;
const write = (nextValue) => value = nextValue;
return [read, write];
}Read/Write
Signals under the hood
const [count, setCount] = createSignal(3);
console.log("Initial Read", count()); // 3
setCount(5);
console.log("Updated Read", count()); // 5
setCount(count() * 2);
console.log("Updated Read", count()); // 10Signals under the hood
const context = [];
function subscribe(running, subscriptions) {
subscriptions.add(running);
running.dependencies.add(subscriptions);
}
export function createSignal(value) {
const subscriptions = new Set();
const read = () => {
const running = context[context.length - 1];
if (running) subscribe(running, subscriptions);
return value;
};
const write = (nextValue) => {
value = nextValue;
for (const sub of [...subscriptions]) {
sub.execute();
}
};
return [read, write];
}Signal and deps tracking
Signals under the hood
function cleanup(running) {
for (const dep of running.dependencies) {
dep.delete(running);
}
running.dependencies.clear();
}
export function createEffect(fn) {
const running = {
execute,
dependencies: new Set()
};
const execute = () => {
cleanup(running);
context.push(running);
try {
fn();
} finally {
context.pop();
}
};
execute();
}
Reactions / Effects
Signals under the hood
console.log("1. Create Signal");
const [count, setCount] = createSignal(0);
console.log("2. Create Reaction");
createEffect(() => console.log("The count is", count()));
console.log("3. Set count to 5");
setCount(5);
console.log("4. Set count to 10");
setCount(10);Signals under the hood
export function createMemo(fn) {
const [s, set] = createSignal();
createEffect(() => set(fn()));
return s;
}Computed/memo
Signals under the hood
console.log("1. Create");
const [firstName, setFirstName] = createSignal("John");
const [lastName, setLastName] = createSignal("Smith");
const [showFullName, setShowFullName] = createSignal(true);
const displayName = createMemo(() => {
if (!showFullName()) return firstName();
return `${firstName()} ${lastName()}`
});
createEffect(() => console.log("My name is", displayName()));
console.log("2. Set showFullName: false ");
setShowFullName(false);
console.log("3. Change lastName");
setLastName("Legend");
console.log("4. Set showFullName: true");
setShowFullName(true);Signals under the hood
const [firstName, setFirstName] = createSignal("Salama");
const [lastName, setLastName] = createSignal("Ashoush");
const [showLastName, setShowLastName] = createSignal(false);
const fullName = createMemo(() => {
if(showLastName()){
return `${firstName()} ${lastName()}`;
}
return firstName();
});
createEffect(() => console.log(fullName())) // ?
setLastName("Salama")
setShowLastName(true)
setLastName("Ashoush")
Dynamic Tracking
Signals under the hood
Signals
state primitives

Nested Reactivity
import { createStore } from "solid-js/store";
// stores are proxy based
const [person, setPerson] = createStore({
name: "Salama",
age: 30,
kids:[
{
name: "Omar",
age: 3,
},
{
name: "Ali",
age: 0.5,
},
]
});
// update a nested
setPerson('kids', 0, 'age', 2.5);
// there is also a mutuble version that allows this
const [person, setPerson] = createMutableStore({...})
kids[0].age = 2.5Signals state primitives
import { component$, useStore } from '@builder.io/qwik';
export default component$(() => {
const state = useStore({
name: "Salama",
age: 32,
kids:[
{
name: "Omar",
age: 5,
},
{
name: "Ali",
age: 2
},
]
});
return (
<>
<button onClick$={() => state.kids[0].age = 2.5}>Update nested</button>
<p>Count: {state.kids[0].age}</p>
</>
);
});
Signals state primitives
Nested Reactivity
import { createResource } from 'solidjs';
// you can pass just a fetch function
const [data, { mutate, refetch }] = createResource(() => fetch("something"));
// or you can pass a signal to be tracked and the fetcher will run when it changes
const [data, { mutate, refetch }] = createResource(someSignal(), () => fetch("something"));
// access the data
data()
// update the data
mutate()
// rerun the fetcher funtion
refetch()Async Reactivity
Signals state primitives
export default component$(() => {
const prNumber = useSignal('3576');
const prTitle = useResource$<string>(async ({ track }) => {
// it will run first on mount (server), then re-run whenever prNumber changes (client)
// this means this code will run on the server and the browser
track(() => prNumber.value);
const response = await fetch(
`https://api.github.com/repos/BuilderIO/qwik/pulls/${prNumber.value}`
);
const data = await response.json();
return data.title as string;
});
return (
<>
<input type="number" bind:value={prNumber} />
<h1>PR#{prNumber}:</h1>
<Resource
value={prTitle}
onPending={() => <p>Loading...</p>}
onResolved={(title) => <h2>{title}</h2>}
/>
</>
);
});Async Reactivity
Signals state primitives
Signals and Components
import { render } from "solid-js/web";
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(1);
const increment = () => setCount(count() + 1);
// since the components only runs once this will lose reactivity
const doubleCount = count() * 2
return (
<button type="button" onClick={increment}>
{doubleCount}
</button>
);
}
render(() => <Counter />, document.getElementById("app")!);
import { render } from "solid-js/web";
import { createSignal, createMemo } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(1);
const increment = () => setCount(count() + 1);
// to fix this you have to ethier wrap it in a thunk
const doubleCount = () => count() * 2
// or createMemo
const doubleCount = createMemo(() => count() * 2)
return (
<button type="button" onClick={increment}>
{doubleCount()}
</button>
);
}
render(() => <Counter />, document.getElementById("app")!);
Signals and Components
import { component$, useSignal } from '@builder.io/qwik';
// this all component will run once on the server and never on the client
// untill user interacts with the button
export default component$(() => {
// to achieve full resumeabilty qwik tie the reactive graph to the component graph
// which means you can't use signals outside of components
const count = useSignal(0);
// unlike solid this will stay reactive
// thanks to the qwik compiler
const doubleCount = count.value * 2
return (
<button onClick$={() => count.value++}>
Increment {count.value}
</button>
);
});Signals and Components
Signals everywhere

import { useSignal, useComputed } from "@preact/signals";
function Counter() {
const count = useSignal(0);
const double = useComputed(() => count.value * 2);
return (
<div>
<p>{count} x 2 = {double}</p>
<button onClick={() => count.value++}>click me</button>
</div>
);
}What about React signals?!

What about React signals?!
// .svelte
<script>
// runes are like compiler macros that add lower level code on compile time
let a = $state(1);
let b = $state(2);
// computed/derived state, compiler will transform this to
// lower level signal primitives
let c = $derived(a + b)
b += 3
// there is also effects
effect$(() => {
console.log(c);
})
</script>
<p>{c}</p>
Svelte v5 Runes
@Component({
signals: true,
selector: 'simple-counter',
template: `
<!-- count is invoked as a getter! -->
<p>Count {{ count() }}</p>
<p>{{ name }}</p> <!-- Not reactive! -->
<button (click)="increment()">Increment count</button>`,
})
export class SimpleCounter {
count = signal(0); // WritableSignal<number>
name = 'Morgan';
increment() {
this.count.update(c => c + 1);
}
}Angular signals
@Component({
signals: true,
selector: 'user-profile',
template: `
<p>Name: {{ firstName() }} {{ lastName() }}</p>
<p>Account suspended: {{ suspended() }}</p>
`,
})
export class UserProfile {
// Create an optional input without an initial value.
firstName = input<string>(); // Signal<string|undefined>
// Create an input with a default value
lastName = input('Smith'); // Signal<string>
// Create an input with options.
suspended = input<boolean>(false, {
alias: 'disabled',
}); // Signal<boolean>
}Angular signals
@Component({
signals: true,
selector: 'form-field',
template: `
<field-icon *ngFor="let icon of icons()"> {{ icon }} </field-icon>
<div class="focus-outline">
<input #field>
</div>
`
})
export class FormField {
icons = viewChildren(FieldIcon); // Signal<FieldIcon[]>
input = viewChild<ElementRef>('field'); // Signal<ElementRef>
someEventHandler() {
this.input().nativeElement.focus();
}
}Angular signals
use leptos::prelude::*;
#[component]
fn App() -> impl IntoView {
let (count, set_count) = signal(0);
view! {
<button
on:click=move |_| set_count.set(3)
>
"Click me: "
{count}
</button>
<p>
"Double count: "
{move || count.get() * 2}
</p>
}
}Finally it is not only JS
Signals are the new v-dom
Thanks
Questions
Copy of Fine-tuning your reactivity: Harnessing the power of signals
By Salama Ashoush
Copy of Fine-tuning your reactivity: Harnessing the power of signals
Fine-tuning your reactivity: Harnessing the power of signals
- 104