Updates and mutations
The tree is immutable. Every mutation — drag-to-resize, dropping a panel,
clicking the remove button — produces a brand-new tree and hands it to
onChange. The library never mutates the value you pass in.
That immutability is made practical by a small set of helpers that take a tree and a path, and return a new tree.
updateTree and MosaicUpdate
Everything funnels through updateTree:
import { updateTree, MosaicUpdate } from 'react-mosaic-component';
const updates: MosaicUpdate<string>[] = [
// flip the root split from row to column
{ path: [], spec: { direction: { $set: 'column' } } },
];
const next = updateTree(tree, updates);
A MosaicUpdate is a { path, spec } pair. The spec is an
immutability-helper
command — $set, $push, $apply, etc. — applied at the node addressed
by path.
You rarely write specs by hand. The library ships a handful of factories that produce the common ones for you.
The update factories
| Factory | What it does |
|---|---|
createRemoveUpdate(tree, path) | Removes the node at path, collapsing its parent if needed. |
createHideUpdate(tree, path) | Temporarily hides the node at path (sets its split percentage to 0). |
createExpandUpdate(path, percentage) | Grows the node at path to take percentage of its parent, at every level up to the root. |
createDragToUpdates(tree, sourcePath, destinationPath, dropInfo) | Produces the full update sequence for a drag-and-drop operation. dropInfo is e.g. { type: 'split', position: 'left' }. |
createDragToUpdates returns an array of updates; the others return a single
MosaicUpdate<T>. Put them together in one array and pass it to
updateTree:
const next = updateTree(tree, [createRemoveUpdate(tree, [1, 0])]);
Example: a "reset layout" button
import { useState } from 'react';
import {
Mosaic,
MosaicWindow,
MosaicNode,
createBalancedTreeFromLeaves,
getLeaves,
} from 'react-mosaic-component';
function App() {
const [tree, setTree] = useState<MosaicNode<string> | null>(INITIAL);
const rebalance = () => {
setTree((current) => createBalancedTreeFromLeaves(getLeaves(current)));
};
return (
<>
<button onClick={rebalance}>Auto arrange</button>
<Mosaic value={tree} onChange={setTree} renderTile={/* ... */} />
</>
);
}
getLeaves walks the tree and returns the leaf keys in order;
createBalancedTreeFromLeaves packs them back into a minimal-depth n-ary
tree. Chaining the two gives you "reshuffle into a clean grid" in one line.
Why not just mutate?
Immutability keeps rendering predictable: React re-renders only when the
reference changes, and because Mosaic is happy to be driven by value,
you get free time-travel. Keep a stack of past trees and you have undo for
the price of one array.
const [history, setHistory] = useState<MosaicNode<string>[]>([INITIAL]);
const tree = history[history.length - 1];
const push = (next: MosaicNode<string>) =>
setHistory((h) => [...h, next]);
const undo = () =>
setHistory((h) => (h.length > 1 ? h.slice(0, -1) : h));
Because mosaic hands you a whole new tree on every onChange, this pattern
is as simple as it looks.