reaffect
v2.2.0
Published
Reactive side-effect container for Javascript apps
Downloads
19
Maintainers
Readme
Reaffect
Reaffect is a reactive side-effect container for Javascript apps.
It allows building apps where side-effects are a function of the current state of the application, and separate from business logic.
Getting started
Installation
npm i reaffect
Defining effects
An effect is just a function which takes a dispatcher function as the first argument, and returns a canceller function.
The dispatcher function takes a value as the first argument, and whether the effect is finished as the second argument.
function SendEachSecond(dispatch, valueToSend) {
const id = setInterval(() => dispatch(valueToSend, false), 1000)
return () => clearInterval(id)
}
Effects can be made higher-order:
const WithLog = (dispatch, f, ...args) => {
const str = JSON.stringify([f.name, ...args])
console.log(`${str} started`)
const cancel = f((value, done) => {
if (!done)
console.log(`${str} sent "${value}"`)
dispatch(value, done)
}, ...args)
return () => { cancel(); console.log(`${str} cancelled`) }
}
const WithTakeN = (dispatch, n, f, ...args) => {
let k = 0
return f((value, done) => dispatch(value, done || k++ < n), ...args)
}
const WithDiscard = (dispatch, f, ...args) =>
f((value, done) => done && dispatch(null, done), ...args)
Activating effects
Activating effects and retrieving their sent values
can be done inside a generator function
or any object with a next
method.
import { reaffect } from 'reaffect'
function* app() {
while (true) {
const msg = yield [
[SendEachSecond, 'hello'],
[WithLog, SendEachSecond, 'world'],
]
console.log(msg)
}
}
reaffect(app())
Generators are composable:
function* screen1() {
while (true) {
const msg = yield [/* ... */]
if (msg === 'screen2')
yield* screen2()
}
}
And can be made higher-order:
const withLogAll = gen => ({
next(v) {
v = gen.next(v)
return { done: v.done, value: v.done || v.value.map(e => [WithLog, ...e]) }
}
})
Examples
React counter
import React from 'react'
import { render } from 'react-dom'
import { reaffect } from 'reaffect'
const SendEverySecond = (dispatch, value) =>
clearInterval.bind(this, setInterval(dispatch, 1000, value))
const Render = (dispatch, count) => {
render(
<div>
<div>{count}</div>
<button onClick={() => dispatch('increase')}>+1</button>
<button onClick={() => dispatch('decrease')}>-1</button>
</div>
, document.getElementById('root')))
return () => dispatch = () => {}
}
function* app() {
let count = 0
while (true) {
const msg = yield [
[Render, count],
count > 0 && [SendEachSecond, 'decrease'],
count < 0 && [SendEachSecond, 'increase'],
]
switch (msg) {
case 'increase':
count++
break
case 'decrease':
count--
break
}
}
}
reaffect(app())
Compatibility with Async Iterators
Although async iterators are not really immediately cancellable,
.return()
will do it after the next promise resolves.
const WrapAsyncIterator = (dispatch, f, ...args) => {
const it = (async () => {
for await (const v of f(...args))
dispatch(v)
dispatch(null, true)
})()
return () => it.return()
}
Acknowledgements
This library is inspired by hyperapp and Elm.
License
MIT