Callbags represent values that change over time:
1linkimport { interval, pipe, map, filter, subscribe } from 'callbag-common'; // @see [callbag-common](https://loreanvictor.github.io/callbag-common/)
2link
3linkconst source = interval(1000); // --> emits every second
4linkpipe(
5link source,
6link map(x => x * 3), // --> multiply by 3
7link filter(x => x % 2), // --> only allow odd numbers
8link subscribe(console.log) // --> log any incoming number
9link);
3
9
15
21
27
👉 If you are familiar with RxJS, you can think of callbags as light-weight observables.
👉 Checkout this post for a more depth introduction to callbags.
local_library CALLBAG LIBRARIES
There are many callbag libraries you can use with
callbag-jsx
. For examples in this documentation, we are going to use callbag-common, which includes a nice set of commonly used callbag utilities and is also installed on the starter templates.
You can create callbag sources using states, using a source factory, or expressions of other callbags:
1linkimport { state } from 'callbag-state';
2linkimport { interval, fromEvent, expr } from 'callbag-common';
3link
4link
5link// a simple counter whose value we manually control:
6linkconst count = state(0);
7link
8link// a timer emitting every second:
9linkconst timer = interval(1000);
10link
11link// this will be the latest mouse event when mouse is moved:
12linkconst mouseEvent = fromEvent(document.body, 'mousemove');
13link
14link// a computed value based on count and timer:
15linkconst computed = expr($ => $(count) * $(timer));
16link
17link// a the X-axis of the mouse:
18linkconst mouseX = expr($ => $(mouseEvent).clientX);
👉 These callbags can now be embedded in DOM to make it change over time as well:
1linkrenderer.render(
2link <div style='height: 100vh'>
3link count: {count}
4link  
5link <span onclick={() => count.set(count.get() - 1)}>⬇️</span>
6link <span onclick={() => count.set(count.get() + 1)}>⬆️</span>
7link <br/>
8link
9link timer: {timer} <br/>
10link timer * count: {computed} <br/>
11link mouseX: {mouseX} <br/>
12link </div>
13link).on(document.body);
You can pipe callbag sources to callbag operators to create new, modified callbags:
1linkimport { state } from 'callbag-state';
2linkimport { pipe, map, debounce } from 'callbag-common';
3link
4linkconst input = state('');
5linkconst len = pipe(
6link input,
7link debounce(200), // --> wait 200 milliseconds after last input change
8link map(s => s.length) // --> map it to its length
9link);
10link
11linkrenderer.render(
12link <>
13link <input _state={input} type='text' placeholder='Type something ...'/>
14link <br/>
15link Input Length: {len}
16link </>
17link).on(document.body);
local_bar PIPELINE OPERATOR
If you are using the JavaScript Starter Template, or Pipeline Operator Plugin for Babel, you can pipe callbags with the pipeline operator (
|>
) instead of thepipe()
utility:
1linkconst len = input |> debounce(200) |> map(s => s.length)
Typically you need to subscribe to callbag sources (i.e. listen to them). In callbag-jsx
this is done
implicitly when you embed callbags in DOM elements:
1linkconst s = state(0);
2linkrenderer.render(<div>{s}</div>).on(document.body);
👉 The subscription only happens AFTER the element is appended to main document:
1linkconst s = state(0);
2linkconst d = <div>{s}</div>; // --> no subscription
3linkrenderer.render(d).on(document.body); // --> subscription happens here
👉 The subscription is cleared when the element is removed from the main document:
1linkconst s = state(0);
2linkconst d = <div>{s}</div>; // --> no subscription
3linkrenderer.render(d).on(document.body); // --> subscription happens here
4link
5linkrenderer.render(
6link <button onclick={() => renderer.remove(d)}>Remove</button> {/* --> subscription ends when this button is clicked */}
7link).on(document.body);
⚠️ IMPORTANT ⚠️
You MUST use
renderer.remove()
method to ensure that the subscriptions of a DOM element are cleared. Otherwise, this might result in a memory leak as subscription resources are not released.
You can also explicitly subscribe to callbags, using subscribe()
utility:
1linkimport { pipe, subscribe } from 'callbag-common';
2linkimport { state } from 'callbag-state';
3link
4linkconst s = state(0);
5linkpipe(s, subscribe(console.log)); // --> logs changes in value of s
👉 Remember to clear manual subscriptions when you no longer need them:
1linkimport { pipe, subscribe } from 'callbag-common';
2linkimport { state } from 'callbag-state';
3link
4linkconst s = state(0);
5linkconst clear = pipe(s, subscribe(console.log));
6link
7link// when everything is done:
8linkclear();