Ripple-Reactive

Core Primitives

Understand and utilize the foundational building blocks of Ripple.js.

Signals

Signals are the primary unit of state in Ripple.js. They hold a value and automatically track any functions or computed values that read them.

Ripple.js provides two different APIs for signals depending on your preference.

The Object API uses a .value property to read and write the state. This is similar to Vue's ref or Preact's signal.

import { signal } from 'ripple-reactive';

const theme = signal('light');

// Read the value
console.log(theme.value); // 'light'

// Mutate the value
theme.value = 'dark';

Computed Values

Computed values derive their state from other signals. They are lazy (they only calculate when read) and memoized (they cache their result until their dependencies change).

import { signal, computed } from 'ripple-reactive';

const price = signal(10);
const quantity = signal(3);

const total = computed(() => price.value * quantity.value);

// The function hasn't run yet.
console.log(total.value); // 30 (Evaluates here)

price.value = 20; 
// The computed value is marked as dirty, but doesn't re-evaluate immediately.

console.log(total.value); // 60 (Re-evaluates here)

By default, computed values check for strict equality (===) before triggering downstream updates. You can provide a custom equals function to objects or arrays to prevent unnecessary renders.

const point = computed(
  () => ({ x: xSignal.value, y: ySignal.value }),
  { equals: (a, b) => a.x === b.x && a.y === b.y }
);

Effects

Effects are side-effects that run automatically whenever their dependent signals change.

import { signal, effect } from 'ripple-reactive';

const status = signal('idle');

// Runs immediately once, then tracks `status.value`
const stop = effect(() => {
  console.log(`Current status: ${status.value}`);
});

status.value = 'running'; // Triggers the effect again

// Dispose the effect when no longer needed
stop();

Effect Cleanup

If your effect creates a subscription or a timer, you can register a cleanup function that runs right before the effect re-evaluates or is destroyed.

effect((onCleanup) => {
  const id = setInterval(() => console.log('Polling...'), 1000);
  
  onCleanup(() => clearInterval(id));
});

Advanced Primitives

Batching

When you mutate multiple signals in a row, Ripple.js normally triggers effects immediately for each mutation. The batch function coalesces these updates so effects only run once.

import { signal, effect, batch } from 'ripple-reactive';

const targetRepo = signal('facebook/react');
const scanStatus = signal('idle');
const vulnerabilitiesFound = signal(0);

effect(() => {
  console.log(`[${scanStatus.value}] ${targetRepo.value} - Issues: ${vulnerabilitiesFound.value}`);
});

// Without batching, the effect would run three times here.
// With batching, it only runs once at the end.
batch(() => {
  targetRepo.value = 'aditya/gitscout';
  scanStatus.value = 'scanning';
  vulnerabilitiesFound.value = 14;
});

Untracking

Sometimes you need to read a signal inside an effect without subscribing to it. Use untrack to read values safely.

import { signal, effect, untrack } from 'ripple-reactive';

const loggedIn = signal(true);
const username = signal('ADITYA SING');

effect(() => {
  if (loggedIn.value) {
    // This effect will NOT re-run if `username` changes, 
    // it only re-runs when `loggedIn` changes.
    const currentName = untrack(() => username.value);
    console.log(`Welcome back, ${currentName}`);
  }
});

On this page