Reactive building blocks for typesafe JSX-based web applications
npm install cytoplasmicCytoplasmic is a simple JSX-based frontend TypeScript library based on reactive programming. Cytoplasmic provides a set of building blocks (reactive cells) and utilities for building web applications.
Reactive programming in Cytoplasmic is implemented using cells. If you have ever used a spreadsheet, you should be familiar with how cells work:
``tsx`
// Create two cells:
const a = cell(1);
const b = cell(2);
// Create a computed cell that uses a and b to compute a value:
const c = zipWith([a, b], (a, b) => a + b);
// Initially the value of c is 3:
console.log(c.value); // 3
// But if we change the value of one of the inputs, the value of c also changes:
a.value = 10
console.log(c.value); // 12
The API documentaion is available here: https://nielssp.github.io/cytoplasmic/
- Counter (7GUIs)
- Temperature Converter (7GUIs)
To set up a minimal Cytoplasmic project using Vite for bundling, you can type the following commands:
``
npx degit nielssp/cytoplasmic/template my-app
cd my-app
npm install
npm run dev
Cytoplasmic allows you to create DOM-elements using JSX-syntax.
`tsx
import { createElement, mount } from 'cytoplasmic';
function HelloWorld() {
return
mount(document.body,
`
Use the mount() function to create an instance of your component and attach it to the DOM. document.body can be used as the root element if you want the entire page to be controlled by your Cytoplasmic component, but any DOM-element can be used as the root if you want to embed a Cytoplasmic component in an existing non-Cytoplasmic application.
`tsx
import { createElement, mount} from 'cytoplasmic';
// A component without inputs
function HelloWorld() {
return
// A component with a required input
function HelloName({name}: {
name: string,
}) {
return
// The main application component
function App() {
return
// Attach application component to the body
mount(document.body,
`
Cells are the reactive building blocks of Cytoplasmic components. There are immutable (Cell) and mutable (MutCell) cells.cell(x)
To create a mutable cell simply call the function where x is the cell's default content.value
To get the current value of the cell the -getter can be used. For mutable cells, the value can be changed with the value-setter.
`tsx`
const a = cell(1);
console.log(a.value); // 1
a.value = 2;
console.log(b.value); // 2
An important cell operator is map() which produces a dependent cell that changes whenever the source cell changes:
`tsx`
const a = cell(1);
const b = a.map(x => x + 1);
console.log(b.value); // 2
a.value = 2;
console.log(b.value); // 3
To compute a value based on multiple source cells, zip and zipWith can be used:
`tsx`
const a = cell(1);
const b = cell(2);
const c = zipWith([a, b], ([a, b]) => a + b);
console.log(c.value); // 3
a.value = 2;
console.log(c.value); // 4
In the following component a count cell keeps track of the number of times the button is clicked:
`tsx
import { createElement, cell } from 'cytoplasmic';
function ClickCounter() {
const count = cell(0);
return
mount(document.body,
`
Cells containing strings, numbers, and booleans can be used directly in JSX using {cell}-notation.
Accepting cells as properties in a component allows the component to react to state changes. The Input type, which is an alias for Cell, can be used to create components that work with both cells and raw values. To create a two-way binding a MutCell can be used instead, this allows the component to send data back via the cell.
`tsx
// A component with two inputs
function Result(props: {
a: Input
b: Input
}) {
// The input() utility is used to turn Input
const a = input(props.a);
const b = input(props.b);
const out = zipWith([a, b], (a, b) => a + b);
return {out}
}
// A component with a string input and a number output
function Incrementor({label, num}: {
label: Input
num: MutCell
}) {
return ;
{label}: {num}
}
function Adder() {
const a = cell(0);
const b = cell(0);
return
Conditionally show elements
The
-component can be used to conditionally show and hide elements:`tsx
function ToggleSection() {
const show = cell(false);
return
Hello, World!
;
}
`The
else-property can be used to show an alternative when the condition is false:`tsx
{show} is false The following utility methods make it easier to work with boolean cells:
*
a.not: True when a.value is falsy, false otherwise
* a.undefined: True when a.value is null or undefined, false otherwise
* a.defined: Opposite of undefined
* a.eq(b): True when a.value strictly equals b.value (b may also be a raw value), false otherwise
* a.and(b): Same as b.value when a.value is truthy, false otherwise
* a.or(b): Same as a.value when a.value is truthy, otherwise the same as b.valueRefCells and MutRefCells are cells that aren't guaranteed to contain a value, i.e. .value may be undefined. To handle such values the -component can be used:`tsx
function Foo() {
// ref() is a shorthand for cell(undefined)
const optionalNumber = ref();
return
There is no number to show!
}>
{ n =>
The number is {n}
}
Deref expects a function that accepts a non-nullable cell and returns an element. The function is called only when the value of the RefCell is not undefined or null. The dereferenced value (
n in the example above) is still a cell (type Cell) however.It's also possible to completely unwrap a cell (i.e. remove reactivity) using the
-component:
`tsx
function Foo() {
const optionalNumber = ref();
return
There is no number to show!
}>
{ n =>
The number is {n}
}
In the above example the type of
n is number as opposed to Cell when using Deref. The difference between using Unwrap and Deref is that whenever the input to Unwrap changes, all DOM elements are recreated, whereas Deref will reuse the DOM elements and simply update the value of the cell.Looping
Looping is done using the
component:`tsx
function ListWithStaticArray() {
const items = [1, 2, 3, 4];
return { item =>
Item: {item}
}
}
`It's possible to loop through an array contained within a cell:
`tsx
function ListWithArrayCell() {
const items = cell([1, 2, 3, 4]);
function addItem() {
items.update(i => i.push(i.length + 1));
}
return
{ item =>
Item: {item}
}
}
`When the cell is updated the
For-component will reuse existing DOM elements, but an update will be triggered for all items in the array. If you need to make lots of small modifications to an array you can use cellArray() instead:`tsx
function ListWithCellArray() {
const items = cellArray([1, 2, 3, 4]);
function addItem() {
items.push(items.length.value + 1);
}
return
{ item =>
Item: {item}
}
}
`In cell arrays each item is a cell which makes it possible to efficiently update the content of individual cells. Additionally removals and insertions are handled efficiently.
Lazily loaded components
The
component can be used to show components loaded lazily via the import()-function:`tsx
{() => import('./my-component').then(m => )}
`Change component dynamically
The
can be used to render a component stored in a cell:`tsx
const component = ref>();component.value = MyComponent;
`This makes it possible to dynamically replace the rendered component with another one. Some possible uses are tab pages, modals, etc.
Utilities
*