Why does effect re-fire? How to use createStore values in effect correctly?
Check out this component. Why does the effect refire when I'm only changing person.name.first? It has something to do with the Proxy object, but how is that useful if any change to any store fires all the effects where any part of the store is referenced?
import type { Component } from "solid-js";
import { createEffect } from "solid-js";
import { createStore, produce } from "solid-js/store";
import type { Person } from "../types/Person";
type Props = {};
export const StoreTest: Component<Props> = () => {
const [person, setPerson] = createStore<Person>({
name: {
first: "Brandon",
last: "Sanderson",
},
age: 45,
});
const changePersonFirstName = (name: string) => {
setPerson(
produce((p: Person) => {
// Immer style store update
p.name.first = name;
})
);
};
createEffect(() => {
// Why does this fire the effect again?
console.log(person);
});
return (
<div>
<h2>Store Test:</h2>
<p>
Person is named {person.name.first} {person.name.last} and he is{" "}
{person.age}
</p>
<button class="btn" onClick={() => changePersonFirstName("Sam")}>
Person first name change
</button>
</div>
);
};
53 Replies
Hmmm, the exact same code work well in the https://playground.solidjs.com/. The console locally only shows a proxy object and refires, in the playground, it shows only the store values and doesn't refire. Wired. Maybe versioning or something...
you can share the playground link btw (button on top right)
It may be an issue with logging
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
console.log is intercepted in the playground, so everything inside it is read
while the way it works in the browser iirc bypasses proxy traps
It works how I would think it would work in the playground. the console prints the {name: {first, last}, age} format thing. But in the browser from my local code, you can see that same console prints the proxy object. Which I don't care about except for the proxy object is registering a change to the root of the object when person.name.first is changed which re-fires the effect based off person changing (the proxy). So there may be a problem with the reactivity in the createStore. Or I'm confused.
I think you are confused, because basically when a signal is read it registers itself with the parent effect/memo, and for stores property accesses are turned into signal reads under the hood for you, but you are just writing this
console.log(person);
, with the built-in console.log
function, not the one the playground gives you, you are not reading anything there, so you are not tracking anything. Just reading person
itself is untrackable, you can't intercept a read like that in JS
if you did like person.firstName etc. then you'd be tracking somethinginteresting. Is there a special console.log that I can use to debug without altering the firing pattern of effects and such?
in the playground? I don't think so
in the code locally
you want to patch your local
console.log
to work like the one in playground?ultimatly, I want to avoid refiring effects based off parts of stores being altered that I'm not dealing with in an effect. For instance, I changed the first name and then the pointer to person registered as changed and the effect depending on person re-fired. Also, would be nice to debug by printing person without the proxy style stuff.
if you want a console.log that doesn't track you can try this:
but the native one shouldn't track I think, so you canjust not use the playground instead
that's cool. And then the idea would be that I only use specific values like person.name.first inside an effect and that effect would track the first name properly and the reactivity would be coorect?
yes, but
person.name.first
tracks both .first
and .name
I don't want to use the playground. I'm trying to write a running project and deploy it. So I need to figure these things out in the running code.
oh first and name aren't individual signals?
doesn't that mean re-rendering parts that don't need to be rerendereed?
no
if
.name
changes then you need to re-execute the effect because you depend on that
if person.job
changes the effect doesn't careso if I have one div showing rendering person.name.first and another div with a reference to person.name.first, when I click a button that changing person.name.first, does the div rendering last re-render? type Person = {name: {first: string, last:string}, age: number}
can you show me some code for this? I'm not following
the only things that can be re-executed are memos and effects, it's impossible to re-execute anything else, including components, divs, roots, and what not
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
does div with id='last' re-render or react when the button is clicked?
only effects and memos can react to something
if you want to check if
person.name.last
, which will be wrapped in an effect automatically by the compiler, reacts to the change, you can write an effect to check for thatSolid Playground
Quickly discover what the solid compiler will generate from your JSX template
neither
person.name
nor person.name.last
are ever changed, so it's not re-executedok
what about this one: https://playground.solidjs.com/anonymous/070e8677-e546-48b1-a68b-b1d1f5dbab44
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
I see the same as you but the person isn't re-fired but does locally. I think you covered this. but can you articulate why, locally, the person effect fires again.
🤔 I had understood that it fired in the playground but not locally, not the other way around
if it fires the other way around then I guess console.log in the playground doesn't actually cause extra things to be tracked, and maybe your local console.log is not the native one? Or the native one causes reads like that to fire and I misremembered?
it looks like the one in the playground is untracked, and the local one doesn't bypass proxy traps as I remembered 🤔
ya playground doesn't fire on console.log(person). Locally I get the proxy object. There is a browser screenshot above with an orange button
you can try what happens in incognito mode to disable extensions
ya sure. I'll try.
ok in private window in Brave with no extensions, the proxy object still appears. When I untrack the console log, the effect doesn't refire. When I use the browsers native console.log(person) the effect does refire on change of person.name.first
Ok then I guess I misrememebred and console.log does cause properties of the store to be tracked
though it might depend if you have the devtools open or not
it may be worth using this untracked console.log for your own debugging
so I guess the root of the question is, if I use an effect with console.log(person), that registeres the proxy object and when people change something like person.name.first, that effect with be re-fired even though person.name.first isn't referenced in the effect?
because person is a complex proxy object? or maybe it's because I'm using 'produce"?
yes there's no compiler magic like that, whatever signal you read inside the effect, in whichever way you do it, will track the signal
because proxy is in object and everything you read inside it will cause a signal to be called internally
is the signal on person and person.name.first the same?
like that's what a store is, it's just a proxy that turns property reads and writes into signals reads and writes internally
each property has a signal behind it
person has no signal behind it because it's not a property
"person" itself can never change, it will always point to the same object
but this object will be mutated
person.name and person.name.first have different signals behind them
ok so first and last are properties and person and name are not. So tracking person will be effected by changes in first?
things are updated in a fine-grained way
only signals can be tracked and there's no signal behind the "person" variable, so I don't understand the question
ok so only first and last are tracked as signals?
every property is a signal
including .name
and whatever else there is on the person object
ok so person isn't a property and doesn't have a signal?
yes
signals are for things that can change, person can never change, so point in making it a signal
I guess person is just the pointer to the store which is a proxy object with all that magic stuff
like "produce" looks like it updates the store immutably immer-style, but that's not how it works, it just updates the store directly by mutating it
yeah
what about a produce that sets person.name = {first: "dan", last: 'swan"}?
would the pointer to name not change?
it would, but the signal behind it is the same, so everything that reads .name will be reexecuted because now the property points to a new object
like basically you subscribe to whatever you read, and when whatever you read changes you are re-executed, if you are an effect or a memo which are the only things that can be reexecuted
ok cool, so long story short, the root object like person in this case is just kind of different that the sub peices and needs to be understood. That's why it, specifically, has a different behaviour for console.log. And if I want to print the root value of a store like the whole person, then I need to untrack that console.log during debugging?
person is a store, and any object you read from it is turned into a store also, and so on, so they work all the same
ok I think I get it.
the thing about console.log is that it looks like it may read properties on the store, and therefor causing them to be tracked 🤷♂️ but the console.log in the playground is a custom one so I guess that has been untracked explicitly
I'm working with a company that is going to do some pilot projects in Solid as an addition to their current React work. So thanks for the help. I'll continue with my soildJs class now. Thanks for the time.
No problem 👍