SkillAgentSearch skills...

Kefir.atom

Composable and decomposable reactive state with lenses and Kefir

Install / Use

/learn @calmm-js/Kefir.atom
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

[ | Motivation | Tutorial | Reference | About ]

This library provides a family of concepts and tools for managing state with lenses and Kefir.

npm version Bower version Build Status Code Coverage

Contents

Motivation

Use of state and mutation is often considered error-prone and rightly so. Stateful concepts are inherently difficult, because, unlike stateless concepts, they include the concept of time: state changes over time. When state changes, computations based on state, including copies of state, may become invalid or inconsistent.

Using this library:

  • You can store state in first-class objects called Atoms.

    • This means that program components can declare the state they are interested in as parameters and share state by passing references to state as arguments without copying state.
  • You can declare decomposed first-class views of state using lenses and composed first-class views of state as Molecules.

    • This means that program components can declare precisely the state they are interested in as parameters independently of the storage of state.
  • You get consistent read-write access to state using get and modify operations at any point and through all views.

    • This means that by using views, both decomposed and composed, of state you can avoid copying state and the inconsistency problems associated with such copying.
  • You can declare arbitrary dependent computations using observable combinators from Kefir as AbstractMutables are also Kefir properties.

    • This means that you can declare computations dependent upon state independently of time as such computation are kept consistent as state changes over time.
  • You can mutate state through multiple views and multiple atomic modify operations in a transactional manner by holding event propagation from state changes.

  • You can avoid unnecessary recomputations, because program components can declare precisely the state they are interested in and views of state only propagate actual changes of state.

    • This means that algorithmic efficiency is a feature of this library rather than an afterthought requiring further innovation.

The rest of this README contains a tutorial to managing state using atoms and provides a reference manual for this library.

Tutorial

Let's write the very beginnings of a Shopping Cart UI using atoms with the karet and via the karet.util libraries.

Karet is simple library that allows one to embed Kefir observables into React VDOM. If this tutorial advances at a too fast a pace, then you might want to read a longer introduction to the approach.

This example is actually a stripped down version of the Karet Shopping Cart example that you can see live here.

Counters are not toys!

So, how does one create a Shopping Cart UI?

Well, of course, the first thing is to write the classic counter component:

const Counter = ({count}) => (
  <span>
    <button onClick={U.doModify(count, R.dec)}>-</button>
    {count}
    <button onClick={U.doModify(count, R.inc)}>+</button>
  </span>
)

The Counter component displays a count, which is supposed to refer to state that contains an integer, and buttons labeled - and + that decrement and increment the count using modify.

As you probably know, a counter component such as the above is a typical first example that the documentation of any respectable front-end framework will give you. Until now you may have mistakenly thought that those are just toys.

Component, remove thyself!

The next thing is to write a component that can remove itself:

const Remove = ({removable}) => (
  <button onClick={U.doRemove(removable)}>x</button>
)

The Remove component gives you a button labeled x that calls remove on the removable state given to it.

Pure and stateless

At this point it might be good idea to point out that both the previous Counter component and the above Remove component are referentially transparent aka pure functions. Furthermore, instances of Counter and Remove are stateless. This actually applies to all components in this tutorial and most components in real-world Calmm applications can also be pure functions whose instantiations are stateless. First-class, decomposable, and observable state makes it easy to store state outside of components and make the components themselves pure and stateless.

Lists are simple data structures

Then we write a higher-order component that can display a list of items:

const Items = ({items, Item}) => (
  <div>
    {U.mapElemsWithIds(
      'id',
      (item, key) => (
        <Item {...{key, item}} />
      ),
      items
    )}
  </div>
)

The Items component is given state named items that is supposed to refer to an array of objects. From that array it then produces an unordered list of Item components, passing them an item that corresponds to an element of the items state array.

Items in a cart

We haven't actually written anything shopping cart specific yet. Let's change that by writing a component for cart items:

const count = [L.removable('count'), 'count', L.defaults(0)]

const CartItem = ({item}) => (
  <div>
    <Remove removable={item} />
    <Counter count={U.view(count, item)} />
    {U.view('name', item)}
  </div>
)

The CartItem component is designed to work as Item for the previous Items component. It is a simple component that is given state named item that is supposed to refer to an object containing name and count fields. CartItem uses the previously defined Remove and Counter components. The Remove component is simply passed the item as the removable. The Counter component is given a lensed view of the count. The count lens makes it so that when the count property reaches 0 the whole item is removed.

This is important: By using a simple lens as an adapter, we could plug the previously defined Counter component into the shopping cart state.

If this is the first time you encounter partial lenses, then the definition of count may be difficult to understand, but it is not very complex at all. It works like this. It looks at the incoming object and grabs all the properties as props. It then uses those to return a lens that, when written through, will replace an object of the form {...props, count: 0} with undefined. This way, when the count reaches 0, the whole item gets removed. After working with partial lenses for some time you will be able to w

View on GitHub
GitHub Stars52
CategoryDevelopment
Updated1y ago
Forks5

Languages

JavaScript

Security Score

85/100

Audited on Dec 30, 2024

No findings