https://github.com/rliang/reaffect
Reactive side-effect container for Javascript apps
https://github.com/rliang/reaffect
functional javascript reactive side-effects
Last synced: 3 months ago
JSON representation
Reactive side-effect container for Javascript apps
- Host: GitHub
- URL: https://github.com/rliang/reaffect
- Owner: rliang
- License: mit
- Created: 2019-02-12T14:39:39.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2019-03-11T19:10:50.000Z (over 7 years ago)
- Last Synced: 2025-10-19T21:27:43.597Z (8 months ago)
- Topics: functional, javascript, reactive, side-effects
- Language: TypeScript
- Size: 88.9 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Reaffect
[](https://www.npmjs.org/package/reaffect)
[](https://bundlephobia.com)
[](https://david-dm.org/rliang/reaffect)
Reaffect is a reactive effect container for Javascript apps.
It allows writing business logic as generator functions
where each yield point specifies a set of active effects,
which are then automatically started, cancelled or kept active.
This means effects are reactive.
That is, they reflect the current state of the application.
- 📚 [API](index.d.ts)
- âš¡ [Examples](example)
## Getting started
### Installation
```sh
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.
```js
function SendEachSecond(dispatch, valueToSend) {
const id = setInterval(() => dispatch(valueToSend, false), 1000)
return () => clearInterval(id)
}
```
In the following examples,
we can see that effects can be wrapped
into higher-order effects that process the events
they receive in some way.
```js
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`) }
}
```
```js
const WithTakeN = (dispatch, n, f, ...args) => {
let k = 0
return f((value, done) => dispatch(value, done || k++ < n), ...args)
}
```
```js
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.
```js
import { reaffect } from 'reaffect'
function* app() {
while (true) {
const msg = yield [
[SendEachSecond, 'hello'],
[WithLog, SendEachSecond, 'world'],
]
console.log(msg)
}
}
reaffect(app())
```
In the following example,
we can see that generators are composable through `yield*`.
```js
function* screen1() {
while (true) {
const msg = yield [/* ... */]
if (msg === 'screen2')
yield* screen2()
}
}
```
The following example wraps a generator
into a higher-order generator
that logs all events it receives.
```js
const withLogAll = gen => ({
next(v) {
let { done, value } = gen.next(v)
return { done, value: done ? undefined : value.map(e => e && [WithLog, ...e]) }
}
})
```
## [Examples](#examples)
### React counter
```js
import React from 'react'
import { render } from 'react-dom'
import { reaffect } from 'reaffect'
const SendEachSecond = (dispatch, value) =>
clearInterval.bind(this, setInterval(dispatch, 1000, value))
const Render = (dispatch, count) => {
render(
{count}
dispatch('increase')}>+1
dispatch('decrease')}>-1
, 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.
```js
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](https://github.com/jorgebucaran/hyperapp/tree/V2)
and [Elm](https://elm-lang.org).