Skip to content

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:

js
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 runs

touch does not change the stored value. It only fires the property’s trigger so subscribers re-run.

Signature ​

js
touch(proxy, key)  →  boolean
ArgumentDescription
proxyReactive object from reactive(), or a component context registered as a touch target by the runtime
keyRoot 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.

js
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 target

Only 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 ​

SituationUse touch?
Nested field on a plain objectYes
Deep field on an object inside an array/Map/SetYes
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 ​

js
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):

js
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:

js
state.items.push(row);    // notifies
state.items.splice(i, 1); // notifies

Deep element edits and index assignment do not:

js
state.items[0].label = "Updated";
touch(state, "items");

state.items[i] = nextRow;
touch(state, "items");

Or replace:

js
state.items = state.items.map((row, index) =>
  index === i ? { ...row, label: "Updated" } : row,
);

Maps and Sets ​

js
// 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" });
js
// 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:

js
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:

js
const [user, setUser, triggerUser] = createSignal({ name: "Ada" });

user().name = "Grace";
triggerUser(); // same role as touch for a single signal

For 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().
js
touch(state, "user"); // only dependents of state.user
// dependents of state.count are unaffected

Patterns ​

Batch nested edits, touch once

js
state.user.name = "Grace";
state.user.age = 37;
touch(state, "user"); // one notification

Touch after external / imperative mutation

js
mutateInPlace(state.draft); // third-party helper
touch(state, "draft");

Prefer replace for simple updates

js
// Clear and readable — no touch needed
state.filters = { ...state.filters, query: q };

API Summary ​

js
import { touch } from "udodi";

const ok = touch(proxy, key);
ItemDetail
proxyReactive object, or component context registered as a touch target
keystring or symbol root property
Returnsboolean — whether the proxy was a known target (key type valid)
SchedulingSame microtask batching as normal writes

Constraints ​

BehaviorDetail
Shallow notifyOnly the named root property’s subscribers
No value changeReference and contents unchanged by touch itself
CollectionsStructural methods already notify the owning property
Unknown keysNo-op if the key has no signal; still returns true for a valid reactive proxy
FailureReturns false for non-reactive targets or invalid key types