Skip to content

Commit

Permalink
Update readme.
Browse files Browse the repository at this point in the history
  • Loading branch information
nielssp committed Apr 21, 2024
1 parent fd3b33a commit 5d990d6
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 11 deletions.
181 changes: 172 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ console.log(c.value); // 4

### Using cells in components to manage state

In the following component a `count` cell keeps track of the number of times the button is clicked:

```tsx
import { createElement } from 'cytoplasmic';

Expand All @@ -120,23 +122,184 @@ function ClickCounter() {
mount(document.body, <ClickCounter/>;
```
Cells containing strings, numbers, and booleans can be used directly in JSX using `{cell}`-notation.
### Cell inputs and outputs
Accepting cells as properties in a component allows the component to react to state changes. The `Input<T>` type, which is an alias for `Cell<T> | T`, can be used to create components that work with both cells and raw values. To create a two-way binding a `MutCell<T>` 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<number>,
b: Input<number>,
}) {
// The input() utility is used to turn Input<T> into Cell<T>
const a = input(props.a);
const b = input(props.b);
const out = zipWith([a, b], (a, b) => a + b);
return <div>{out}</div>
}

// A component with a string input and a number output
function Incrementor({label, num}: {
label: Input<string>,
num: MutCell<number>,
}) {
return <div>
{label}: {num}
<button onClick={() => num.value++}>
+1
</button>
</div>;
}

function Adder() {
const a = cell(0);
const b = cell(0);
return <div>
<Incrementor label='A' num={a}/>
<Incrementor label='B' num={b}/>
<div>Result:</div>
<Result a={a} b={b}/>
</div>;
}
```
## Conditionally show elements
* `<Show>`
* `<Deref>`
* `<Unwrap>`
The `<Show>`-component can be used to conditionally show and hide elements:
## Dynamically show elements
```tsx
function ToggleSection() {
const show = cell(false);
return <div>
<button onClick={() => show.value = !show.value}>
Show
</button>
<Show when={show}>
<div>
Hello, World!
</div>
</Show>
</div>;
}
```
* `<Lazy>`
* `<Dynamic>`
The `else`-property can be used to show an alternative when the condition is false:
```tsx
<Show when={show} else={<div>{show} is false</div>}>
<div>This is shown when {show} is true</div>
</Show>
```
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.value`
`RefCell`s and `MutRefCell`s are cells that aren't guaranteed to contain a value, i.e. `.value` may be undefined. To handle such values the `<Deref>`-component can be used:
```tsx
function Foo() {
// ref<T>() is a shorthand for cell<number | undefined>(undefined)
const optionalNumber = ref<number>();
return <div>
<button onClick={() => optionalNumber.value = 5}>Set a number</button>

<Deref ref={optionalNumber} else={<div>There is no number to show!</div>}>
{ n =>
<div>The number is {n}</div>
}
</Deref>
</div>
}
```
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<number>`) however.
It's also possible to completely unwrap a cell (i.e. remove reactivity) using the `<Unwrap>`-component:
```tsx
function Foo() {
const optionalNumber = ref<number>();
return <div>
<button onClick={() => optionalNumber.value = 5}>Set a number</button>

<Unwrap from={optionalNumber} else={<div>There is no number to show!</div>}>
{ n =>
<div>The number is {n}</div>
}
</Unwrap>
</div>
}
```
In the above example the type of `n` is `number` as opposed to `Cell<number>` 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
* `<For>`
* `cellArray()`
* `cellMap()`
Looping is done using the `<For>` component:
```tsx
function ListWithStaticArray() {
const items = [1, 2, 3, 4];
return <For each={items}>{ item =>
<div>Item: {item}</div>
}</For>
}
```
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 <div>
<For each={items}>{ item =>
<div>Item: {item}</div>
}</For>
<button onClick={addItem}>Add an item</button>
</div>
}
```
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() {
i.push(i.length.value + 1);
}

return <div>
<For each={items}>{ item =>
<div>Item: {item}</div>
}</For>
<button onClick={addItem}>Add an item</button>
</div>
}
```
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.
## Dynamically show elements
* `<Lazy>`
* `<Dynamic>`
## Utilities
Expand Down
8 changes: 6 additions & 2 deletions src/cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,12 @@ class BimappingCell<T1, T2> extends MutCell<T2> {

export type Input<T> = Cell<T> | T;

export function input<T>(input: Input<T>): Cell<T> {
if (input instanceof Cell) {
export function input<T>(input: Input<T>): Cell<T>;
export function input<T>(input: Input<T> | undefined, defaultValue: T): Cell<T>;
export function input<T>(input: Input<T>, defaultValue?: T): Cell<T> {
if ((input === undefined || input === null) && defaultValue) {
return new ConstCell(defaultValue);
} else if (input instanceof Cell) {
return input;
}
return new ConstCell(input);
Expand Down

0 comments on commit 5d990d6

Please sign in to comment.