1.0.0 • Published 9 months ago

@hackbg/atom v1.0.0

Weekly downloads
-
License
-
Repository
-
Last release
9 months ago

@hackbg/atom

Overview

This library defines the atom primitive, which represents an observable value that can have one or more display representations (instances).

import atom from '@hackbg/atom'

How to use

Creating

To create an atom, you need one argument, the formatter: a function that returns the canonical representation of the atom's value.

const fmt1 = x=>`${x||0} bottles of beer on the wall`

Calling the atom function will create an atom, as a a "callable object" (a.k.a. "JS function with custom properties").

const foo = atom(fmt1)

Value

You can access the atom's value using the get and set methods:

expect(foo.get() === undefined)
expect(foo.set(99).get() === 99)

Calling an atom with no arguments returns the formatted value:

expect(foo() === `99 bottles of beer on the wall`)

Formatter

The formatter can be changed at runtime:

const bar = atom(fmt1)
bar.set(99)
expect(bar() === `99 bottles of beer on the wall`)

bar.format = (current, previous) =>
  `${current.bottles} bottles of beer on the wall`
    + ((previous?.bottles) ? ` (was ${previous.bottles})` : '')
bar.set({ bottles: 98 })
expect(bar() === `98 bottles of beer on the wall`)

bar.set({ bottles: 97 })
expect(bar() === `97 bottles of beer on the wall (was 98)`)

Updating

Calling an instance's update method re-renders that instance and returns the rendered value (e.g. the root of the rendered DOM component).

When setting an atom's value, if the new value is not equal to the old, setting the value calls update, which updates all displayed instances of the value. When the value of an atom is an object and you make deep changes to it, the new value remains equal to the old value, so you need to call update:

bar.get().bottles -= 2
bar.update()

Instances

Calling an atom with one argument (the render function) creates a new instance of the atom.

The render function of an instance takes the formatted value and returns a final rendered representation, e.g. a DOM element.

An instance is a display representation of an atom. There can be multiple instances of each atom.

const bar1 = bar(y => `[ ${y.split('(')[0]} ]`)
const bar2 = bar(y => `{ ${y.split('(')[0]} }`)
expect(bar1.rendered === `[ 95 bottles of beer on the wall ]`)
expect(bar2.rendered === `{ 95 bottles of beer on the wall }`)

bar.set({ bottles: 94 })
expect(bar1.rendered === `[ 94 bottles of beer on the wall ]`)
expect(bar2.rendered === `{ 94 bottles of beer on the wall }`)

Detaching

Upon creation, each instance is added to the atom's private instance collection. This way, when the atom's value is changed, it knows to re-render all its instances. Use the detach method to permanently stop updates to a particular instance.

bar2.detach() === bar2
bar.set({ bottles: 93 })
expect(bar1.rendered === `[ 93 bottles of beer on the wall ]`)
expect(bar2.rendered === `{ 94 bottles of beer on the wall }`)

bar.set({ bottles: 92 })
expect(bar1.rendered === `[ 92 bottles of beer on the wall ]`)
expect(bar2.rendered === `{ 94 bottles of beer on the wall }`)

Renderers

Instance renderers can also be changed at runtime:

const oldRenderer = bar2.renderer
bar2.renderer = (...args) => `STOPPED: ${oldRenderer(...args)}`
expect(bar2.update().rendered === `STOPPED: { 94 bottles of beer on the wall }`)

Testing

The code blocks in this README constitutes this micro-library's test suite. See @hackbg/ensuite.

function expect (condition) { if (!condition) throw new Error('assertion failed') }
1.0.0

9 months ago