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
dsmurl
dsmurlOP2y ago
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...
thetarnav
thetarnav2y ago
you can share the playground link btw (button on top right)
fabiospampinato
It may be an issue with logging
dsmurl
dsmurlOP2y ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
fabiospampinato
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
dsmurl
dsmurlOP2y ago
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.
fabiospampinato
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 something
dsmurl
dsmurlOP2y ago
interesting. Is there a special console.log that I can use to debug without altering the firing pattern of effects and such?
fabiospampinato
in the playground? I don't think so
dsmurl
dsmurlOP2y ago
in the code locally
fabiospampinato
you want to patch your local console.log to work like the one in playground?
dsmurl
dsmurlOP2y ago
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.
fabiospampinato
if you want a console.log that doesn't track you can try this:
import {untrack} from 'solid-js';

const log = console.log;
console.log = ( ...args ) => {
untrack ( () => {
log ( ...args );
});
};
import {untrack} from 'solid-js';

const log = console.log;
console.log = ( ...args ) => {
untrack ( () => {
log ( ...args );
});
};
but the native one shouldn't track I think, so you canjust not use the playground instead
dsmurl
dsmurlOP2y ago
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?
fabiospampinato
yes, but person.name.first tracks both .first and .name
dsmurl
dsmurlOP2y ago
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?
fabiospampinato
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 care
dsmurl
dsmurlOP2y ago
so 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}
fabiospampinato
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
dsmurl
dsmurlOP2y ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
dsmurl
dsmurlOP2y ago
does div with id='last' re-render or react when the button is clicked?
fabiospampinato
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 that
fabiospampinato
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
fabiospampinato
neither person.name nor person.name.last are ever changed, so it's not re-executed
dsmurl
dsmurlOP2y ago
ok
dsmurl
dsmurlOP2y ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
dsmurl
dsmurlOP2y ago
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.
fabiospampinato
🤔 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 🤔
dsmurl
dsmurlOP2y ago
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
fabiospampinato
you can try what happens in incognito mode to disable extensions
dsmurl
dsmurlOP2y ago
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
dsmurl
dsmurlOP2y ago
fabiospampinato
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
dsmurl
dsmurlOP2y ago
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"?
fabiospampinato
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
dsmurl
dsmurlOP2y ago
is the signal on person and person.name.first the same?
fabiospampinato
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
dsmurl
dsmurlOP2y ago
ok so first and last are properties and person and name are not. So tracking person will be effected by changes in first?
fabiospampinato
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
dsmurl
dsmurlOP2y ago
ok so only first and last are tracked as signals?
fabiospampinato
every property is a signal including .name and whatever else there is on the person object
dsmurl
dsmurlOP2y ago
ok so person isn't a property and doesn't have a signal?
fabiospampinato
yes signals are for things that can change, person can never change, so point in making it a signal
dsmurl
dsmurlOP2y ago
I guess person is just the pointer to the store which is a proxy object with all that magic stuff
fabiospampinato
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
dsmurl
dsmurlOP2y ago
what about a produce that sets person.name = {first: "dan", last: 'swan"}? would the pointer to name not change?
fabiospampinato
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
dsmurl
dsmurlOP2y ago
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?
fabiospampinato
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
dsmurl
dsmurlOP2y ago
ok I think I get it.
fabiospampinato
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
dsmurl
dsmurlOP2y ago
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.
fabiospampinato
No problem 👍
Want results from more Discord servers?
Add your server