Skip to content

Bloomca/vellyr

Repository files navigation

Vellyr

npm i vellyr

This is a small signal library with fairly minimal API surface. The signals are not lazy, so all derived signals will be automatically re-computed on any sources change.

To mitigate that, you can pass a custom comparator when creating/deriving a new signal. Here is an example of how you can use it:

import { vellyr } from "vellyr";

const state$ = vellyr({
  items: [
    { active: true, name: "first" },
    { active: false, name: "second" },
  ],
});

const filteredIds$ = state$.map((state) =>
  state.items
    .filter((item) => item.active)
    .map((item) => item.id),
  { equality: shallowEqual }
);

filteredIds$.on(ids => {
    console.log(`active ids: ${ids}`)
})

Creating signals

  • vellyr(initialValue)
  • vellyr(initialValue, { equality: (value1, value2) => value1.id === value2.id })
  • vellyr.only(value)
  • vellyr(vellyr.empty)

By default, you probably want to provide the first value, and you can also provide the equality function. You can create a signal which cannot change its value (it simply does not have set() or update() calls), but can be used to derive values. Finally, you can manually provide an empty value, which will stop derived signals like .combine() from being triggered before you set the first value.

Reading and writing values

Signals provide straightforward imperative API to interact with the current value and to update it.

  • signal$.get()
  • signal$.getError() (more on the errors later)
  • signal$.set(value)
  • signal$.update(currentValue => currentValue + 1)

Deriving signals

The power of signals (or any observable primitive for that matter) comes from their composable nature. This library aims for a very small set of available methods to derive new signals from existing ones, but it should be enough for common use-cases and extending them.

  • signal$.map(value => value * 2)
  • signal$.map(item => sanitizeItem(item), { equality: equalityFn })

Map allows you to transform an existing signal into something else, which is very useful. You can provide a custom equality function, and the signal you are deriving from can have its own equality function as well.

  • clicks$.combine(item$, request$).map(([click, item, request]) => ...)

Combining multiple signals simply creates a signal with each value as a tuple. You cannot provide a custom equality function to this method (if you really need one, you can map the result with the identity and provide an equality function at this point).

  • values$.filter(value => value > 5)

Signal which will only receive a new value if the filtered function returns true. You can provide the equality function here as well.

  • const sum$ = values$.scan((acc, value) => acc + value, 0)

To achieve reduce functionality, use .scan() to store the accumulated value. You can also provide the custom equality function as the last argument.

Subscribing

To actually listen to value changes, there is .on() method, which comes in 2 flavours:

  • const unsub = value$.on((value) => { ... })
  • const unsub = value$.on({ next: ( value) => { ... }, error: e => { ... } })

They are equivalent, but the second form is the only way to subscribe for errors. The errors are propagated down, so an error on a parent signal will propagate down the children.

Disposing

To clean up parent/children dependencies correctly, you need to call signal$.dispose() when the signal is not relevant anymore. This is needed because parents/children need special tracking internally so that combining multiple signals with the same parent will only be updated once.

About

Small signals library

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages