Snapshots
Overview
Snapshots are immutable, structurally shared, representations of tree nodes (models and their children).
Snapshots in mobx-bonsai
mainly serve these two purposes:
- As a serialization / deserialization mechanism (be it to store it or send it over the wire).
- As a way to bridge data to non-
mobx-react
-enabled React components.
Basically, when a change is performed over a tree node then a new immutable snapshot of it will be generated. Additionally, immutable snapshots for all parents will be generated as well. Any unchanged objects however will keep their snapshots unmodified.
For example, imagine a model A
with two children (B
and C
), and let's call their initial snapshots sA[0]
, sB[0]
and sC[0]
.
A -> sA[0] = getSnapshot(A)
B -> sB[0] = getSnapshot(B)
C -> sC[0] = getSnapshot(C)
If we change a property in B
then a new snapshot will be generated for it, as well as for all its parents (A
), but not for unaffected objects (C
in this case), thus resulting in:
A -> sA[1] = getSnapshot(A)
B -> sB[1] = getSnapshot(B)
C -> sC[0] = getSnapshot(C)
This means, as mentioned before, that snapshots generation is automatically optimized to only change their references when the objects they represent (and their children) actually change.
Note: Never change the contents of a snapshot object returned by getSnapshot
directly, clone it first! If you do weird things might happen.
Snapshot Functions
getSnapshot
The function getSnapshot
creates a deep, immutable copy of a node in the observable data tree. This snapshot provides a plain JavaScript representation of the state at the time of calling, ensuring that further changes to the observable do not affect the snapshot.
Note that snapshots maintain referential integrity: when you update a primitive in the root node, only the top-level reference is replaced. In contrast, if you update a primitive inside a nested child node, the reference of that child and each of its parent nodes (up to the root) will be updated. This behavior guarantees that changes are accurately tracked at the proper hierarchical level.
Using getSnapshot
can be useful when you need a consistent view of the state for debugging, logging, or diffing purposes.
Note: getSnapshot
only works for nodes that are currently part of the data tree. If a node is not part of the data tree the returned snapshot will be undefined
.
For example, you can capture the current state of your application like this:
const currentState = getSnapshot(todoAppState);
The currentState
will be a plain JavaScript object containing the data from todoAppState
, and any changes made afterwards to todoAppState
will not impact currentState
.
You can also capture a snapshot of any nested node in your observable tree:
const firstTodoSnapshot = getSnapshot(todoAppState.todoList[0]);
This produces an immutable copy of the todo item.
onSnapshot
Since that is a very common pattern, mobx-bonsai
offers an onSnapshot
function that will call a listener with the new snapshot and the previous snapshot every time it changes.
const disposer = onSnapshot(todo, (newSnapshot, previousSnapshot) => {
// do something
})
In both cases the returned disposer function can be called to cancel the effect.
applySnapshot
The function applySnapshot
applies an immutable snapshot to a tree node. It reconciles the current state with the provided snapshot while enforcing:
- For arrays, the target must be an array.
- For objects, the target must be observable and its
nodeType
andnodeKey
properties must match the snapshot. - Snapshots containing Maps or Sets are rejected.