Cells: Simplified reactive functional programming for the web
Functional & Reactive programming (FRP) simplifies handling complex, dynamic
data flows: It is particularly useful in scenarios with asynchronous data
sources, such as user interfaces or real-time data feeds.
cells
is a simplified Functional & Reactive (but not strictly FRP) library
inspired by spreadsheets.
cells
focuses on automation as it transparently manages:
async
calls- undefined values
- errors
- pointers
Although it is a fully independent library, cells
can be a powerful drop-in
replacement for Svelte stores as it implements the Svelte store interface. There
is also a React hook
and a demo with Vue 3.
Walkthrough
import { Sheet } from "@okcontract/cells";
const sheet = new Sheet();
const cellA = sheet.new(1);
const cellB = sheet.new(getValueAfterDelay(2, 100));
const sum = sheet.map([cellA, cellB], (a, b) => a + b);
const prod = cellB.map((v) => 2 * v);
expect(await prod.get()).toBe(4);
cellA.set(3);
expect(await sum.get()).toBe(5);
const ok = someCell.map(async (v) => {
const errors = await validate(v);
return errors.length === 0;
});
Note that:
.get()
never returns undefined
and cells
semantics waits until cell
values are defined. If you need to immediately return a value, use null
instead.- At any time, the actual cell value is accessible through
cell.value
(possibly undefined) but it's advisable to avoid relying on a value that
could be updated at any moment.
Subscriptions
You can define subscribers functions to be called whenever a cell value
change.
const cellA = sheet.new(1);
const unsubscribe = cellA.subscribe((v) => {
console.log({ v });
});
unsubscribe();
When using cells
in Svelte files, you can use the $cell
sugar to subscribe
to a cell value for display. Note that there is a initial undefined
value
when using that sugar as the reactive subscription are initially output by the
Svelte compiler as let $cell;
.
Unlike Svelte, subscriptions are called in transactional batches, i.e. cells
wait until all updates are propagated in the Sheet
to dispatch notifications
once to subscribers. This prevents stuttering.
Memory management and proxies
Cells are not garbage collected automatically, since the best practice is to
keep a long-running Sheet
for each application.
To delete a cell, use:
sheet.delete(cell);
To simplify these operations, you can define a sub-graph of cells in a Proxy
that can be deleted at once.
import { SheetProxy } from "@okcontract/cells";
const proxy = new SheetProxy(sheet);
const cell = proxy.new(1);
const mappedCell = proxy.map([...cells], (...args)=>{...})
proxy.destroy();
There are added benefits of proxies, including a single call to wait for all
cells in a Proxy
to be computed.
await proxy.working.wait();
Pointers
Sometimes, you will need to return cells inside .map
compute functions.
cells
manages this automatically with pointers.
const cellA = proxy.new(...);
const cellB = proxy.new(...);
const pointerCell = proxy.map([cellA, cellB], (a, b) =>
condition ? cellA : cellB
);
await pointerCell.get();
pointerCell.map(...);
Cell updates
It's easy to update cells depending on their previous value:
const counter = proxy.new(0);
counter.update((prev) => prev + 1);
Note that update
functions should return new arrays and objects, for
instance using spread operators [...prev, new]
and {...prev, field: new}
.
If you need more complex imperative updates, we suggest you to use
immer:
import { produce } from "immer";
const patch = (prev) => {
};
cell.update((prev) => produce(prev, patch));
Design & Philosophy
The design and philosophy of cells
comes from frameworks such as RxJS
,
MobX
, and Recoil
. Our main objective is to enhance developer experience by
simplifying the complexity typically associated with state management and
reactive programming.
We aim for ease of use and correction, so chasing down any bug is our top
priority.
A non-goal is high-performance (we're not a tensor library!): cells
is
slower than any direct implementation and should not be used for
computationally intensive tasks. However, cells
optimizes the global
computation within a program, potentially leading to more optimal execution
than can be achieved through imperative computations from manually organized
functions or modules.
About
cells
is built at OKcontract and is released under
the MIT license.
Contributors are welcome, feel free to submit PRs directly for small changes.
You can also reach out in our Discord or
contact us on Twitter in advance for larger
contributions.
This work is supported in part by a RFG grant from
Optimism.