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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@adamantjs/signals

v0.2.0

Published

A lightweight reactive programming library.

Downloads

183

Readme

@adamantjs/signals

A lightweight reactive programming library based on the signals design pattern which has been popularised by many modern frontend JavaScript frameworks.

This implementation ships with both CommonJS and ESM versions and is intended to work in any runtime. The module is is tree-shakable, however using all functions should cost less than 500 bytes in a minified and gzipped bundle.

Usage

signal

Creates a signal and assigns it an initial value. Returns a tuple consisting of a getter and a setter which you can name anything you like:

import { signal } from '@adamantjs/signals';

const [count, setCount] = signal(1);

console.log(count()); // logged: 1

setCount(2);
console.log(count()); // logged: 2

setCount(count() * 2);
console.log(count()); // logged: 4

Typed signals

A signal can be of any type. If you use TypeScript, you can specify a type T like so:

import { signal } from '@adamantjs/signals';

type Fruit = 'Apple' | 'Banana' | 'Orange';

const [fruit, setFruit] = signal<Fruit>('Apple');

effect

This is where it gets interesting. effect creates a reactive callback. The callback is executed once immediately. From that point onwards it will be executed again when a value of any of the signals it depends on changes.

import { effect, signal } from '@adamantjs/signals';

const [count, setCount] = signal(1);

effect(() => {
  // whenever the value changes, log it
  console.log(count());
});

// increment the count every second
setInterval(() => {
  setCount(count() + 1);
}, 1000);

Unsubscribing

Our effect will continue to be called forever. We need to manually unsubscribe when our effect is no longer needed. The effect function returns an unsubscribe function we can use. For instance, you might call this in a component's lifecycle "destroy" or "unmount" hook.

import { effect, signal } from '@adamantjs/signals';

const [count, setCount] = signal(1);

const unsubscribe = effect(() => {
  // whenever the count has changed, log it
  console.log(count());
});

// increment the count every second
setInterval(() => {
  setCount(count() + 1);
}, 1000);

// unsubscribe after 5 seconds
setTimeout(() => {
  unsubscribe();
}, 5000);

derived

This lets you create a special signal of which the value is derived from other signals. derived takes a callback, similar to effect, however it expects a return value.

import { derived, effect, signal } from '@adamantjs/signals';

const [count, setCount] = signal(1);

const doubled = derived(() => {
  return count() * 2;
});

effect(() => {
  // whenever the derived value has changed, log it
  console.log(doubled());
});

// increment the count every second
setInterval(() => {
  setCount(count() + 1);
}, 1000);

If a signal changes its value and no one is around to hear it, does it make a sound? The answer is no! Derived signals will subscribe to their dependencies and re-calculate their value only if they themselves are actively subscribed to.

You can use derived signals to create other derived signals, making them very flexible and powerful. You can create entire computed signal chains where values and computations propagate only to where they are subscribed.

import { derived, effect, signal } from '@adamantjs/signals';

const [count, setCount] = signal(1);

const doubled = derived(() => {
  return count() * 2;
});

const quadrupled = derived(() => {
  return doubled() * 2;
});

effect(() => {
  console.log(quadrupled());
});

setInterval(() => {
  setCount(count() + 1);
}, 1000);

Timing and tick

[!IMPORTANT] To maximize efficiency, effects and derived signals are not recalculated until the call stack completes.

import { effect, signal } from '@adamantjs/signals';

const [count, setCount] = signal(1);

effect(() => {
  // whenever the value changes, log it
  console.log(count());
});

setInterval(() => {
  // this entire call stack will complete before the effect is called
  setCount(count() + 1); // this value is not logged!
  setCount(count() + 1);
}, 1000);

Sometimes you need a dependent effect or derived signal to recalculate before you can continue. You can use the tick function for this. tick will await any pending notifications:

import { effect, signal, tick } from '@adamantjs/signals';

const [count, setCount] = signal(1);

effect(() => {
  // whenever the value changes, log it
  console.log(count());
});

setInterval(async () => {
  setCount(count() + 1);
  await tick(); // now the value is logged
  setCount(count() + 1);
}, 1000);

signalToStore (Svelte)

Svelte users with a matching svelte peer dependency may use the specialised export @adamantjs/signals/svelte. This exposes an additional function signalToStore which allows Svelte components to reactively read the signal via $.

<script>
  import { signal, signaltoStore } from '@adamantjs/signals/svelte';

  const [countSignal, setCount] = signal(0);

  const count = signalToStore(countSignal);
</script>

<button on:click={() => setCount($count + 1)}>{ $count }</button>