Wraps value in a proxy that turns every property into a
signal, recursively. Both existing and new properties are
tracked and mutable; reads of keys that don't exist yet are still
tracked, so once such a key is added its readers re-run. Arrays,
Map, and Set are proxied too, with their contents made mutable as
well.
mutable(obj) modifies obj in place, so the original reference
stays reactive, and identity lookups such as .includes(original) or
.indexOf(original) keep matching — reads hand back the proxy, but
the proxied methods normalize their argument to that same proxy. Pass
true as the second argument to copy the input first, leaving the
original untouched. For the non-recursive, proxy-free version see
signalify; for a store with a single batched write
path see store.
| name | type | description |
|---|---|---|
value |
T |
value to make reactive; non-objects are returned as-is |
clone? |
boolean |
when true, copy value first |
Returns: a deeply reactive proxy — over value itself when
clone is omitted, over a copy when clone is true.
Nested properties are reactive too — bumping state.deep.count
re-runs the readers that read it.
import { render } from 'pota'
import { mutable } from 'pota/store'
const state = mutable({ deep: { count: 0 } })
function App() {
return (
<div>
<button on:click={() => state.deep.count++}>increment</button>
<pre>{() => state.deep.count}</pre>
</div>
)
}
render(App)
By default mutable wraps the original in place, so identity
references survive. Pass true to copy first — the original stays
untouched, but identity references are not preserved.
import { render } from 'pota'
import { mutable } from 'pota/store'
// default: keep the reference to m1
const m1 = {}
const s1 = mutable([m1])
render(<pre>in place keeps reference: {String(s1.includes(m1))}</pre>)
// clone: lose the reference to m2
const m2 = {}
const s2 = mutable([m2], true)
render(<pre>copy loses reference: {String(s2.includes(m2))}</pre>)