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?
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?
Share Improve this question edited Aug 26, 2022 at 7:20 clencat asked Aug 22, 2022 at 3:56 clencatclencat 1595 bronze badges2 Answers
Reset to default 6SolidJS will not keep track of individual properties of a signal object.
Also you are overwriting the object every time a value changes when destructuring it (aka. {...obj, }).
If you want createEffect to keep track of your individual properties try createStore.
Here's your example written with createStore instead of createSignal
import {createStore} from 'solid-js/store'; // <- This is very important
const App: Component = () => {
const [obj, setObj] = createStore({
name: "John",
age: 30,
})
createEffect(() => console.log(obj.name));
return (<button onClick={setObject('age', obj.age + 1)}>+</button>)
)
}
if you run this example, you'll see that console.log(obj.name) will only run once, this is because solid-js will keep track of individual properties when saving them on a createStore
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 pared 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 useproduce
, 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")!);
- Use
on
utility fromsolid-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")!);
- You can use equals argument in createSignal:
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.