npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-refsignal

v2.2.0

Published

A lightweight React hook library for managing and subscribing to signals within refs, enabling efficient updates and notifications without unnecessary renders.

Downloads

785

Readme

react-refsignal

CI codecov React >=18.0.0 npm version npm downloads bundlephobia MIT License

Mutable signal-like refs for React where every consumer dictates its own subscription contract — what to observe, when to react, whether to trigger a re-render — independently, per call site.

The same signal can drive a 60 FPS canvas in one place, a throttled HUD label elsewhere, and a normal React component watching a derived boolean — at the same time, with no coordination between them.

Built for UIs where React's render cycle is the bottleneck: node editors (n8n-class), real-time simulations, drag-heavy canvases. Scales down cleanly to ordinary state.

Live demo → — drag the nodes; sixty FPS, zero React re-renders.

Why

Most React state libraries are producer-driven: the store decides when consumers are notified, and selectors, equality functions, or observer wrappers narrow it from there. The producer dictates the contract.

refsignal inverts that. The signal is just a value with a channel. Each consumer, at its call site, decides three things independently:

  • What to observe — the whole signal, a projection, a derived value
  • When to react — synchronous, throttled, debounced, rAF, or a custom filter
  • Whether to render — pure side-effect, or opt into a React re-render

Take a draggable node in a canvas editor. Its position updates sixty times a second. One consumer redraws the connecting curve every frame (rAF-paced, no render). Another updates a HUD label, throttled to 100 ms (no render). A third logs to analytics every second. A fourth is a React component watching a derived isOnscreen boolean — re-renders only when that flips. Same signal, four contracts, no coordination.

Producers can be time-driven too: a pulse signal ticks on a schedule ('1000ms', '60fps', 'raf') and slots into the same model — one shared timer per cadence, lazily started, with each consumer rate-limiting on top.

That model is why refsignal holds 60 FPS where a conventional store crawls below 1 FPS in a dense node-editor benchmark: high-frequency consumers don't pay for the render policy of low-frequency ones. Reconciliation isn't on the path unless a consumer explicitly opts in. Outside high-frequency scenarios the same model scales down — components opt into re-renders explicitly via useRefSignalRender([signal]), and nothing renders elsewhere.

useRef gets you out of the render cycle, but a ref is silent — nothing can subscribe. Build the subscription channel yourself and you're writing this library from scratch. refsignal is that primitive: a ref with a per-consumer subscription channel, built on stable, public React APIs (useSyncExternalStore for renders, direct listeners for effects). No compiler, no proxy, no patched internals.

Installation

npm install react-refsignal

Requires React ≥ 18.0.0.

Quick Start

The model shines when you want updates without re-renders — like driving a canvas from a game loop. A pulse signal ticks every frame; the canvas redraws on each tick; React's render cycle is never involved.

import { useRef } from 'react';
import { usePulseRefSignal, useRefSignalEffect } from 'react-refsignal';

function GameCanvas() {
  const loop = usePulseRefSignal('raf');
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useRefSignalEffect(() => {
    const ctx = canvasRef.current?.getContext('2d');
    if (!ctx) return;
    ctx.clearRect(0, 0, 800, 600);
    ctx.fillRect(loop.tick, 0, 20, 20);
  }, [loop]);

  return <canvas ref={canvasRef} width={800} height={600} />;
}

The same model scales down to ordinary state — opt into a re-render exactly where you want one:

import { useRefSignal, useRefSignalRender } from 'react-refsignal';

function Counter() {
  const count = useRefSignal(0);

  // This component re-renders when count updates
  useRefSignalRender([count]);

  return (
    <button onClick={() => count.update(count.current + 1)}>
      {count.current}
    </button>
  );
}

Without useRefSignalRender, count.update() updates the value and notifies subscribers — but the component never re-renders. That is the point: renders are opt-in.

The producer can also be a clock — pulse signals tick on a schedule and slot into the same graph:

import { createPulseRefSignal, useRefSignalRender } from 'react-refsignal';

// Module scope: one timer for the whole app, lazily started by the first reader.
const now = createPulseRefSignal('60000ms'); // every minute

function RelativeTime({ at }: { at: number }) {
  useRefSignalRender([now]);
  return <time>{formatAgo(at, now.current)}</time>;
}

Mount fifty <RelativeTime /> cells; you get fifty subscribers on one setInterval. Unmount them all and the timer stops. The timer never installs at all if no consumer subscribes.

Per-consumer subscription

Every consumer of a signal picks its own what / when / whether at the call site. The signal doesn't know — and doesn't care — what its consumers do.

Same position signal, four contracts, no coordination between them:

const position = useRefSignal({ x: 0, y: 0 });

// 1. Redraw a curve every frame — no React render
useRefSignalEffect(() => {
  drawCurve(canvasRef.current, position.current);
}, [position], { rAF: true });

// 2. Update a HUD label, throttled to 100 ms — no React render
useRefSignalEffect(() => {
  hudRef.current!.textContent = `${position.current.x}, ${position.current.y}`;
}, [position], { throttle: 100 });

// 3. Log to analytics, debounced to fire on idle — no React render
useRefSignalEffect(() => {
  analytics.track('position', position.current);
}, [position], { debounce: 1000 });

// 4. A derived boolean — re-render only when it flips
const isOnscreen = useRefSignalMemo(
  () => position.current.x >= 0 && position.current.x < viewportWidth,
  [position],
);
useRefSignalRender([isOnscreen]);

Adding or removing any one of these doesn't affect the others. Reconciliation is on the path only for consumer #4 — and only when the boolean actually flips.

Every hook that subscribes (useRefSignalEffect, useRefSignalRender, useRefSignalMemo, and the framework-agnostic watch()) accepts the same options: throttle, debounce, rAF, filter, maxWait. The producer never participates in the timing decision.

Docs

Organized by intent, not by API surface.

Start here

  • Decision Tree — pick the right API for any scenario (signal creation, derived values, persistence, broadcast, context, batching). Doubles as a generation reference for AI tools.

Foundations

  • ConceptsRefSignal vs ReadonlyRefSignal vs ComputedSignal, notify() vs notifyUpdate(), effect vs render, signal lifetime.
  • API Reference — every hook and function with examples.

Building with it

  • Pulse — time-driven signals: clocks, frame loops, "X ago" timestamps, adaptive cadences via updatePulse.
  • Patterns — draggable graphs, signal stores, collections, batching, high-frequency consumers, filtered renders, the sibling-leaf pattern, cross-tab notification badges.
  • Imperative renderers — Canvas / Pixi / WebGL / audio driven by signals, bypassing React reconciliation.
  • Persist — persist signals across page loads (localStorage, sessionStorage, IndexedDB, custom adapters).
  • Cross-tab Broadcast — sync signals across tabs.

Built for AI-assisted coding

The docs/ folder ships inside the npm package — installed at node_modules/react-refsignal/docs/. Cursor, Claude Code, and other LLM-backed editors read it directly, no GitHub fetch required.

The Decision Tree is intentionally written as a generation reference: when an AI assistant asks "which API fits here?", it has a deterministic answer instead of guessing between useRefSignal, useRefSignalEffect, useRefSignalRender, useRefSignalMemo, and a store. Fewer wrong-shape suggestions, fewer stray re-renders.

Concepts

| Concept | Summary | |---|---| | RefSignal<T> | A mutable ref with .update(), .reset(), .subscribe(), a lastUpdated counter, and an optional interceptor | | useRefSignal vs createRefSignal | Inside a component vs anywhere else — both produce the same signal | | useRefSignalEffect vs useRefSignalRender | Imperative side effects vs triggering React re-renders | | notify() vs notifyUpdate() | Fire subscribers without or with bumping lastUpdated | | createComputedRefSignal / useRefSignalMemo | Derived signals — recompute whenever deps change; module-scope or component-scoped | | watch(signal, listener, options?) | Subscribe outside React and get a cleanup function back — mirrors useEffect return pattern; accepts the same filter and timing options as the hooks | | EffectOptions | Gate and rate-limit re-renders and effects via filter, throttle, debounce, maxWait, or rAF | | createPulseRefSignal / usePulseRefSignal | A signal that ticks on a schedule — '1000ms', '60fps', 'raf'. Lazy: the timer runs only while subscribed. Carries dt, tick, elapsed metadata | | updatePulse(rate) | Change a pulse signal's cadence reactively — drive it from another signal for adaptive heartbeats, backoff, perf-budgeted frames | | createRefSignalStore / useRefSignalStore | Provider-free global store — create at module scope, use in any component with renderOn opt-in | | createRefSignalContext | Per-subtree store with auto-generated Provider and hook — for isolated state per route or section | | Signal lifetime | Listeners are in a WeakMap — GC'd when the signal has no references | | Cross-tab broadcast | Sync signals across tabs via react-refsignal/broadcast — zero cost if unused | | Persist | Persist signal values across page loads via react-refsignal/persistlocalStorage, sessionStorage, IndexedDB, or custom adapter |

See Concepts for the full explanation of each.

Global stores

createRefSignalStore creates a module-scope singleton store — no Provider required. useRefSignalStore connects any store to a component with the same renderOn, timing, and unwrap options as the context hook.

import { createRefSignalStore, useRefSignalStore, createRefSignal } from 'react-refsignal';

const gameStore = createRefSignalStore(() => ({
  score: createRefSignal(0),
  level: createRefSignal(1),
}));

// Outside React — direct access
gameStore.score.update(42);

// In a component — opt into re-renders explicitly
function ScoreDisplay() {
  const store = useRefSignalStore(gameStore, { renderOn: ['score'] });
  return <div>{store.score.current}</div>;
}

// Unwrapped — plain value + auto-generated setter
function ScoreEditor() {
  const { score, setScore } = useRefSignalStore(gameStore, {
    renderOn: ['score'],
    unwrap: true,
  });
  return <button onClick={() => setScore(score + 1)}>{score}</button>;
}

Composes with persist and broadcast — wrap the factory before passing it in:

import { persist } from 'react-refsignal/persist';

const gameStore = createRefSignalStore(
  persist(() => ({ score: createRefSignal(0) }), { key: 'game' }),
);

Use createRefSignalContext instead when you need per-subtree isolation — a separate store instance per Provider mount (different routes, multiple widget instances).

How it compares

| Library | Subscribe without re-render | Subscription model | Default reactivity | |---|---|---|---| | react-refsignal | Yes — via useRefSignalEffect | Direct listeners | Off — opt in via useRefSignalRender | | @preact/signals-react | Yes — patches React internals | Auto-tracked | On — any signal read in render | | Jotai | No | Atom-based | On — useAtom triggers re-renders | | Zustand | No | Selector-based | Narrowable via selector, but always renders | | MobX | No | Observable graph | On within observer() wrapper | | Valtio | No | Proxy snapshots | On — proxy auto-tracks | | Redux | No | Selector-based | Narrowable via selector, but always renders | | useRef (plain React) | N/A | None | No subscription possible |

react-refsignal is the only entry that can subscribe to a value via a stable React API and not re-render at all. It's also the only entry where each consumer picks its own subscription rate — synchronous, throttle, debounce, rAF, or a custom filter — at the call site, independently of the producer.

The closest alternative is @preact/signals-react. Both libraries let you update values outside React's render cycle and subscribe to those updates. The difference is how:

@preact/signals-react patches React internals to make signal-driven DOM updates bypass the diffing algorithm entirely — components can update without React knowing. This is powerful but relies on undocumented React APIs that can break across React versions.

react-refsignal uses only stable, public React APIs: useSyncExternalStore for render-triggered subscriptions and direct listener callbacks for side effects. Opting into a re-render is explicit — you call useRefSignalRender or useRefSignalEffect, React handles the rest normally. There is no patching, no magic, no special compiler. The tradeoff is that automatic DOM diffing bypass is not possible — but in most real-world high-frequency scenarios (canvas, WebGL, audio), you are already doing imperative work outside the DOM anyway, which is exactly what useRefSignalEffect is designed for.

If you want fully automatic signal-to-DOM binding with zero boilerplate, @preact/signals-react is worth considering. If you want an explicit, composable model that stays within React's contract, react-refsignal is the right fit.

License

MIT