Morph supports rendering collections with JSX .map() expressions. Lists are reactive: when the state array changes, only the affected items are updated (keyed reconciliation).
Render an array of items with {array.map(item => <JSX>)}:
import { morphState } from 'morph'
export default function App() {
const [items, setItems] = morphState(["a", "b", "c"])
return (
<div>
{items.map(item => <div>{item}</div>)}
</div>
)
}Each item in the array produces one element. The map body must be a JSX element (wrap multiple elements in a fragment):
{items.map(item => (
<>
<span>{item.name}</span>
<span>{item.price}</span>
</>
))}Add a key prop to help Morph match items across updates:
const [items, setItems] = morphState([
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
])
<div>
{items.map(item => <div key={item.id}>{item.name}</div>)}
</div>Keys let Morph reuse existing nodes when items are inserted, removed, or reordered — the items that didn't change are not re-created. Without a key, items are matched by position.
Note:
keymust be a string or number.key={item.id}andkey={item.name}both work.
State arrays are immutable. Create a new array with spread syntax:
<button onClick={() => setItems([...items, { id: items.length, name: "Item" + items.length }])}>
Add
</button>[...items, x] appends x to the end of the array.
Use spread plus slice:
// remove the last item
<button onClick={() => setItems([...items].slice(0, -1))}>
Remove
</button>
// remove the first item
<button onClick={() => setItems([...items].slice(1))}>
Remove First
</button>slice(start, end) supports negative indices — slice(0, -1) keeps everything except the last element.
Map to a new array:
// mark a single item done
<button onClick={() =>
setItems(items.map(item =>
item.id === id ? { ...item, done: true } : item
))
}>
Mark Done
</button>Per-item conditionals work inside the map body:
const [show, setShow] = morphState(true)
<div>
<button onClick={() => setShow(!show)}>Toggle</button>
{items.map(item => (
<div key={item.id}>
{item.name}
{show ? <span> +</span> : null}
</div>
))}
</div>show ? <span> +</span> : null renders the <span> only when show is true, and removes it when false. The && form also works:
{item.done && <span> Done</span>}An empty array renders nothing:
const [items, setItems] = morphState([])For an empty-state message, use a conditional alongside the list:
{items.length === 0 && <div>No items yet</div>}
{items.map(item => <div key={item.id}>{item.name}</div>)}import { morphState } from 'morph'
export default function App() {
const [items, setItems] = morphState([])
const [show, setShow] = morphState(true)
return (
<div>
<button onClick={() => setItems([...items, { id: items.length, name: "Item" + items.length }])}>Add</button>
<button onClick={() => setItems([...items].slice(0, -1))}>Remove</button>
<button onClick={() => setShow(!show)}>Toggle</button>
{items.map(item => (
<div key={item.id}>
{item.name}
{show ? <span> +</span> : null}
</div>
))}
</div>
)
}