const App: Component = () => {
const [obj, setObj] = createSignal({
name: "John",
age: 30,
})
createEffect(
on(
() => obj().name,
(value) => {
console.log("name", value)
}
)
)
return ()=>(<button onClick={()=> setObj(obj=> ({ ...obj, age: obj.age + 1}))}>+</button>)
}
When I change age
, createEffect
will also be triggered, I just want to listen on name
, similar to watch
in Vue3
.
function setup(){
const obj = reactive({
name: "John",
age: 30,
})
watch(()=> obj.name,(value)=>{ console.log("name", value) })
}
Any good ideas?
Signals are atomic values providing unidirectional data flow, meaning you update a signal by setting a new value. Even if you pass the same properties, you will be setting a new object, because objects are compared by reference in JavaScript, hence triggering an update.
That being said, there are multiple ways or workarounds to keep track of an individual property of an object stored in signal.
Using a store. createStore
uses a proxy internally and provides ways to track individual properties. You can use produce
, an Immer inspried API for localized mutations hence update certain properties only.
You can create a memoized signal that tracks an individual property on an object. Here only changing the age re-runs the effect however updating the name has no effect:
import { render } from "solid-js/web";
import { createSignal, createMemo, createEffect } from "solid-js";
function App() {
const [person, setPerson] = createSignal({ name: "John Doe", age: 20 });
const handleClick = () => setPerson({ name: "Jenny Doe", age: 20 });
const age = createMemo(() => person().age);
createEffect(() => {
console.log("Age is updated", age());
});
return (
<button type="button" onClick={handleClick}>
{JSON.stringify(person())}
</button>
);
}
render(App, document.getElementById("app")!);
on
utility from solid-js
:import { render } from "solid-js/web";
import { createSignal, createEffect, on } from "solid-js";
import { createStore } from "solid-js/store";
function App() {
const [person, setPerson] = createStore({ name: 'John Doe', age: 30 });
const increment = () => setPerson(p => ({ ...p, age: p.age + 1 }));
createEffect(on(() => person.name, () => {
console.log('Tracking name');
}));
createEffect(on(() => person.age, () => {
console.log('Tracking age');
}));
return (
<button type="button" onClick={increment}>
{person.name} {person.age}
</button>
);
}
render(App, document.getElementById("app")!);
This method also works for a signal:
import { render } from "solid-js/web";
import { createSignal, createEffect, on } from "solid-js";
function App() {
const [person, setPerson] = createSignal({ name: 'John Doe', age: 30 });
const increment = () => setPerson(p => ({ ...p, age: p.age + 1 }));
createEffect(on(() => person.name, () => {
console.log('Tracking name');
}));
createEffect(on(() => person().age, () => {
console.log('Tracking age');
}));
return (
<button type="button" onClick={increment}>
{person().name} {person().age}
</button>
);
}
render(App, document.getElementById("app")!);
import { render } from "solid-js/web";
import { createSignal, createEffect, on } from "solid-js";
function App() {
const [person, setPerson] = createSignal(
{ name: "John Doe", age: 20 },
{ equals: (prev, next) => prev.age === next.age }
);
const handleClick = () => setPerson({ name: "Jenny Doe", age: 20 });
createEffect(() => {
console.log("Age is updated", person());
});
return (
<button type="button" onClick={handleClick}>
{JSON.stringify(person())}
</button>
);
}
render(App, document.getElementById("app")!);
These are out of the box solution but since Solid is pretty flexible and quite permissive you can implement your own logic if you like.