Using touch()
touch(proxy, key) notifies dependents of a root reactive property after you mutate data in place without replacing that property’s reference.
Use it when shallow reactivity does not see the change on its own.
Why touch() Exists
Reactive objects are shallow. Nested plain objects and deep fields inside collections are not auto-tracked:
import { reactive, effect, touch } from "udodi";
const state = reactive({
user: { name: "Ada", age: 36 },
});
effect(() => {
console.log(state.user.name);
});
state.user.name = "Grace"; // no notification by itself
touch(state, "user"); // effect runstouch does not change the stored value. It only fires the property’s trigger so subscribers re-run.
Signature
touch(proxy, key) → boolean| Argument | Description |
|---|---|
proxy | Reactive object from reactive(), or a component context registered as a touch target by the runtime |
key | Root property name (string or symbol) |
Returns true when the proxy is a known reactive target (or registered alias) and the key type is valid. Returns false when the key is not a string/symbol, or the proxy is not reactive / not registered.
touch(state, "user"); // true when state is reactive
touch(state, "missing"); // true if state is reactive (trigger no-ops for unknown keys)
touch({}, "user"); // false — not a reactive targetOnly properties that were present when reactive() was called have signals. Touching a key that was never reactive has no subscribers to notify.
When to Use It
| Situation | Use touch? |
|---|---|
| Nested field on a plain object | Yes |
| Deep field on an object inside an array/Map/Set | Yes |
Array index assignment (items[i] = x) | Yes (or prefer methods / replace) |
Structural collection method (push, set, …) | No — already notifies |
Replacing the root property (state.user = next) | No — setter notifies |
Primitive write (state.count++) | No — setter notifies |
Nested Objects
const state = reactive({
settings: {
theme: "light",
density: "comfortable",
},
});
effect(() => {
applyTheme(state.settings.theme);
});
state.settings.theme = "dark";
touch(state, "settings");Immutable-style alternative (no touch):
state.settings = {
...state.settings,
theme: "dark",
};Both approaches notify dependents of settings. Prefer replacement when it stays readable; use touch when in-place mutation is required (performance, shared references, third-party APIs).
Arrays
Structural methods already notify:
state.items.push(row); // notifies
state.items.splice(i, 1); // notifiesDeep element edits and index assignment do not:
state.items[0].label = "Updated";
touch(state, "items");
state.items[i] = nextRow;
touch(state, "items");Or replace:
state.items = state.items.map((row, index) =>
index === i ? { ...row, label: "Updated" } : row,
);Maps and Sets
// Map value object mutated in place
const entry = state.meta.get("user");
entry.name = "Grace";
touch(state, "meta");
// Or replace the entry
state.meta.set("user", { ...entry, name: "Grace" });// Object identity in a Set — mutation does not change membership
for (const user of state.selected) {
user.active = true;
}
touch(state, "selected");Component Context
Inside component methods, this is the public context. The runtime registers that context as a touch target for the component’s reactive state, so you can call touch on this directly:
methods: {
rename(next) {
this.user.name = next;
touch(this, "user");
},
},You can also touch the underlying reactive state if you hold a reference to it; the public context form is the usual pattern in components.
Signals
Raw signals expose an equivalent idea as the third tuple element:
const [user, setUser, triggerUser] = createSignal({ name: "Ada" });
user().name = "Grace";
triggerUser(); // same role as touch for a single signalFor reactive objects, prefer touch(proxy, key) so you target a specific property’s subscribers.
What touch Does Not Do
- It does not deep-walk the object or invent subscriptions to nested fields.
- It does not replace or clone the property value.
- It does not notify other keys — only the key you pass.
- It does not create a reactive property for keys that were never installed by
reactive().
touch(state, "user"); // only dependents of state.user
// dependents of state.count are unaffectedPatterns
Batch nested edits, touch once
state.user.name = "Grace";
state.user.age = 37;
touch(state, "user"); // one notificationTouch after external / imperative mutation
mutateInPlace(state.draft); // third-party helper
touch(state, "draft");Prefer replace for simple updates
// Clear and readable — no touch needed
state.filters = { ...state.filters, query: q };API Summary
import { touch } from "udodi";
const ok = touch(proxy, key);| Item | Detail |
|---|---|
proxy | Reactive object, or component context registered as a touch target |
key | string or symbol root property |
| Returns | boolean — whether the proxy was a known target (key type valid) |
| Scheduling | Same microtask batching as normal writes |
Constraints
| Behavior | Detail |
|---|---|
| Shallow notify | Only the named root property’s subscribers |
| No value change | Reference and contents unchanged by touch itself |
| Collections | Structural methods already notify the owning property |
| Unknown keys | No-op if the key has no signal; still returns true for a valid reactive proxy |
| Failure | Returns false for non-reactive targets or invalid key types |