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

@spred/core

v0.37.2

Published

Reactive programming library

Downloads

36

Readme

Spred

npm codecov gzip size

Simple and fast JavaScript reactive programming library.

  • Small. 2 KB minified and gzipped. No dependencies
  • Fast. No unnecessary calculations and excellent performance
  • Simple. Small API and autotracking of dependencies
  • Well typed. Written in TypeScript

API Reference

Basic Example

import { signal, effect, batch } from '@spred/core';

const formatter = new Intl.DateTimeFormat('en-GB');

const name = signal('Paul');
const instrument = signal('bass');
const birthday = signal('1942-06-18');

const formattedBirthday = signal(() =>
  formatter.format(new Date(birthday.get()))
);

effect(() =>
  console.log(
    `Hello. My name is ${name.get()}, I play ${instrument.get()} ` +
      `and I was born on ${formattedBirthday.get()}.`
  )
);
// > Hello. My name is Paul, I play bass and I was born on 18/06/1942.

batch(() => {
  name.set('Ringo');
  instrument.set('drums');
  birthday.set('1940-07-07');
});
// > Hello. My name is Ringo, I play drums and I was born on 07/07/1940.

All examples on StackBlitz

Installation

npm install @spred/core --save

Signals

Signal is the basic reactive primitive of the library. A signal stores a value and notifies its subscribers when it changes. To create a writable signal you should call the signal function with an initial value that is not a function.

import { signal, on } from '@spred/core';

const counter = signal(0);

To get the current value of the signal, you need to call the get method.

/*...*/

console.log(counter.get());
// > 0

To set a new value of a writable signal, you should call the set method with the new value.

/*...*/

counter.set(1);
console.log(counter.get());
// > 1

A call of the signal function with a function argument will create a computed signal that automatically tracks dependencies and recalculates its own value when they change. The return value of the passed computation function must only depend on other signals.

/*...*/

const doubleCounter = signal(() => counter.get() * 2);
console.log(doubleCounter.get());
// > 2

Signal value updates can be subscribed to using the subscribe method. The second argument of the method specifies whether the function should be called immediately after subscribing, and defaults to true. The method returns the unsubscribe function.

/*...*/

const unsub = doubleCounter.subscribe((value) =>
  console.log('Double value is ' + value)
);
// > Double value is 2

counter.set(2);
// > Double value is 4

unsub();
counter.set(3);
// Nothing

console.log(doubleCounter.get());
// > 6

You can also subscribe to a signal value updates without immediately executing the subscriber using on function, which is a shorthand for someSignal.subscribe(someFn, false).

/*...*/

on(doubleCounter, (value) => console.log('Double value is ' + value));
// Nothing

counter.set(4);
// > Double value is 8

Computed signals initialize their values lazily. That means that the calculation function triggers only when the signal has at least one subscriber / dependent signal with a subscriber.

Batching Updates

Writable signal updates are immediate and synchronous.

import { signal, batch, on } from '@spred/core';

const a = signal(0);
const b = signal(0);
const sum = signal(() => a.get() + b.get());

sum.subscribe((s) => console.log('a + b = ' + s));
// > a + b = 0

a.set(1);
// > a + b = 1

b.set(1);
// > a + b = 2

You can commit several updates as a single transaction using the batch function.

/*...*/

batch(() => {
  a.set(2);
  b.set(2);
});
// > a + b = 4

All updates inside subscriber functions and computations are batched by default.

const trigger = signal(0);

on(trigger, () => {
  a.set(3);
  b.set(3);
});

trigger.set(1);
// > a + b = 6

Change Detection

By default all signals trigger their dependents and subscribers only if its value changes.

import { signal } from '@spred/core';

const counter = signal(0);
const doubleCounter = signal(() => counter.get() * 2);

const unsub = doubleCounter.subscribe((value) =>
  console.log('Double value is ' + value)
);
// > Double value is 0

counter.set(0);
// Nothing

counter.set(1);
// > Double value is 2

unsub();

Signals use Object.is to compare values, but you can set custom equality function in signal options.

/*...*/

const obj = signal(
  { value: 1 },
  {
    equal: (a, b) => a.value === (b && b.value),
  }
);

obj.subscribe((obj) => console.log('Object value is ' + obj.value));
// > Object value is 1

obj.set({ value: 1 });
// Nothing

obj.set({ value: 2 });
// > Object value is 2

Undefined values are ignored and can be used for filtering.

/*...*/

const oddCounter = signal(() => {
  if (counter.get() % 2) return counter.get();
});

oddCounter.subscribe((value) => console.log('Odd value is ' + value));
// > Odd value is 1

counter.set(2);
// Nothing

counter.set(3);
// > Odd value is 3

Effects

effect calls the passed function immediately and every time its dependencies change.

import { signal, effect, batch } from '@spred/core';

const a = signal('Hello');
const b = signal('World');

const dispose = effect(() => {
  console.log(`${a.get()} ${b.get()}!`);
});
// > Hello World!

batch(() => {
  a.set('Foo');
  b.set('Bar');
});
// > Foo Bar!

dispose();
a.set('Hello');
// Nothing

Under the hood, the effect is simply a computed signal that becomes active at the moment of creation.

Lifecycle Hooks

Every signal has lifecycle hooks whose handlers can be set in the signal options.

  • onCreate - the signal is created;
  • onActivate - the signal becomes active (has at least one subscriber / dependent signal with a subscriber);
  • onDectivate - the signal becomes inactive (doesn't have any subscriber / dependent signal with a subscriber);
  • onUpdate - the signal updates its value;
  • onException - an unhandled exception occurs during the signal computation.

Example on StackBlitz

Integration

Svelte

Spred signals implement Svelte store contract so you don't need any additional package to use them in Svelte apps.

Example on StackBlitz

React

Use the spred-react package.

Example on StackBlitz

References

Big thanks for inspiration to