S
SolidJSβ€’7mo ago
TomDaBomb

Tagged Template Literals - Why is unwrapped props value reactive?

The docs at https://github.com/solidjs/solid/tree/main/packages/solid/html indicate that reactive variables must be manually wrapped when using tagged template literals, yet this basic example still works, https://playground.solidjs.com/anonymous/45f46e73-ac35-4455-a03b-f8501027fe23 Clarification: I am referring to line 25. It is unwrapped yet is still reactive. I was just curious, why is this? Expected behavior: Label2 shouldn't be reactive. Actual behavior: Label2 is reactive.
GitHub
solid/packages/solid/html at main Β· solidjs/solid
A declarative, efficient, and flexible JavaScript library for building user interfaces. - solidjs/solid
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
29 Replies
peerreynders
peerreyndersβ€’7mo ago
Change it to
function App() {
return html`
<div>
<${Label1} value=${count} />
<${Label2} value=${count} />
</div>
`;
}
function App() {
return html`
<div>
<${Label1} value=${count} />
<${Label2} value=${count} />
</div>
`;
}
and see what happens https://codepen.io/peerreynders/pen/eYoaoXV?editors=0010 The example passes props to the component as unwrapped functions, not wrapped functions.
TomDaBomb
TomDaBombOPβ€’7mo ago
Clarification: I was referring to line 25. It is unwrapped yet is still reactive. (updated my question)
No description
TomDaBomb
TomDaBombOPβ€’7mo ago
NOTE: There is even a warning to wrap it, but it actually still works. And, I thought this was very interesting and was curious about what was happening here.
No description
bigmistqke
bigmistqkeβ€’7mo ago
It's re-executing the component, which also causes all the html-elements to be created again on each prop change.
bigmistqke
bigmistqkeβ€’7mo ago
it would be the same as
function Label3(props) {
const value = props.value
return <div>{value}</div>
};
function Label3(props) {
const value = props.value
return <div>{value}</div>
};
This also 'works' in the same sense as your example: it will be reactive, because the component re-executes on every props.value change. But it will also re-create a div-element everytime. see https://playground.solidjs.com/anonymous/8e54e752-52d6-4e34-8d83-bb9d1c335655
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
peerreynders
peerreyndersβ€’7mo ago
I was referring to line 25
I was aware of that. I pointed out where your example deviated from the instructions. https://github.com/solidjs/solid/tree/main/packages/solid/html#readme
return html`<${Button} type="button" onClick=${increment}>${count}<//>`;
return html`<${Button} type="button" onClick=${increment}>${count}<//>`;
count is a signal and increment a derived value. Both of them are passed as unwrapped functions. Your example passes a function that returns the unwrapped function (unnecessarily). Back to the instructions about wrapping:
html`<div id=${() => props.id}>${() => firstName() + lastName()}</div>`
html`<div id=${() => props.id}>${() => firstName() + lastName()}</div>`
Observations: 1. It's a <div>, a leaf node, so reactive values can't be passed down any further this is where the value they represent actually needs to appear. 2. firstName() + lastName() composes two values (presumably this is what β€œreactive expression” means). This needs to be wrapped in a function. 3. props.id is usually a getter for a primitive value, not a function. So for the whole subscription mechanic to work it needs to be wrapped in a function.
GitHub
solid/packages/solid/html at main Β· solidjs/solid
A declarative, efficient, and flexible JavaScript library for building user interfaces. - solidjs/solid
peerreynders
peerreyndersβ€’7mo ago
Without looking at the source code the rule seems to be if something is a function β€œrun it to get the value out of it and subscribe for further changes” (perhaps even recursively). - firstName() + lastName() isn't a function so we need to turn it into one. - props.id isn't a function (and doesn't return one) so we need to turn it into one to get the value out of it. In your code
function App() {
return html`
<div>
<${Label1} value=${() => count} />
<${Label2} value=${() => count} />
</div>
`;
};
function App() {
return html`
<div>
<${Label1} value=${() => count} />
<${Label2} value=${() => count} />
</div>
`;
};
with () => count you somehow managed to deliver a function via props.value which by the rules hypothesized would be run.
return html`<div>${props.value}</div>`;
return html`<div>${props.value}</div>`;
If you would have followed the instructions by the letter you should have written
function App() {
return html`
<div>
<${Label1} value=${count} />
<${Label2} value=${count} />
</div>
`;
};
function App() {
return html`
<div>
<${Label1} value=${count} />
<${Label2} value=${count} />
</div>
`;
};
Which results in exactly the behaviour that the instructions predicted. The other potential issue was that you were using a "no-build" approach in a "build" playground which may mysteriously "fix" certain errors (which wasn't the case here; but that is why I chose codepen, not the playground).
peerreynders
peerreyndersβ€’7mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
bigmistqke
bigmistqkeβ€’7mo ago
oops ur right, i was too fast w my answer
with () => count you somehow managed to deliver a function via props.value which by the rules hypothesized would be run.
this is the correct explanation: they are in fact wrapped, but done at the props-level instead of inside the template.
peerreynders
peerreyndersβ€’7mo ago
I'm not intimately familiar with the source code; I just wondered if I needed to adjust my mental model.
bigmistqke
bigmistqkeβ€’7mo ago
no i think u were spot on!
peerreynders
peerreyndersβ€’7mo ago
"Blind chicken" and all that πŸ™‚
TomDaBomb
TomDaBombOPβ€’7mo ago
@peerreynders ahhh, I see what you were doing now. Yes, line 25 indeed does not work as I originally hypothesized after changing the way the prop is passed in. πŸ™‚ I assumed that a getter, in itself, was still a "reactive expression" and hence needed to be wrapped. But, I suppose that is not the case! Also, @bigmistqke, my impression was that Solid doesn't re-render entire components. Are there exceptions to this? πŸ‘€ Also, @peerreynders, in the example you presented with props.id, if props.id is a getter, isn't a getter (to a primitive value) still a function? I think it's odd that a signal getter doesn't have to be wrapped, yet when a props value is assigned a signal getter, it has to be wrapped. Strange to me... I think it's fair to say that I have a gap in my knowledge about reactivity. πŸ€” ^ I'm also foregoing using build tools which may complicate things a bit (for learning purposes) (typescript is certainly useful for productivity, but I'm also a bit of a vanilla purist at heart πŸ˜†). PS thanks for the examples πŸ™‚ appreciated!
peerreynders
peerreyndersβ€’7mo ago
if props.id is a getter, isn't a getter (to a primitive value) still a function?
const target = {
get id(){
return 'THX1138';
}
};
console.log(typeof target.id); // "string"

const handler = {
get(target, prop, receiver) {
return target[prop];
},
};

const prop = new Proxy(target, handler);
console.log(typeof prop.id); // "string"
const target = {
get id(){
return 'THX1138';
}
};
console.log(typeof target.id); // "string"

const handler = {
get(target, prop, receiver) {
return target[prop];
},
};

const prop = new Proxy(target, handler);
console.log(typeof prop.id); // "string"
https://www.typescriptlang.org/play/?#code/FAYw9gdgzgLgBDAhgJwOYFN4F44G9hxwbwCWAJgBQCU+hhymArshHAOQAqAEgBoCMfAMwAONgG4CcAL7ApE0JChgANugB0ysKgowAngAd0YAGYIUxNeSpi4AeltwARLGQkIqR8AXR4AC0QQZKrIcDi0RJg65pgANHD6yGD6cQwg6CQAbujIVHiS9EwsZmiYANoJSQC6EoRSMbLy4D7xifqhcBDoAO5wAAqJAB66USUwcf6BwdZeTUqqGlo6BkamFfqWZNZ2Ds4wru6OQA
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
peerreynders
peerreyndersβ€’7mo ago
I think it's odd that a signal getter doesn't have to be wrapped,
It doesn't have to be wrapped when you are passing it as a prop; it's simply the means to acquire (and subscribe) to the value; in that sense it's like a callback. When you need the actual value that it represents you need to wrap it: a) to get the value to do something with b) to subscribe to any changes that may be happening later to the value.
bigmistqke
bigmistqkeβ€’7mo ago
Also, @bigmistqke, my impression was that Solid doesn't re-render entire components. Are there exceptions to this? πŸ‘€
ye i was like completely off. forget anything i said haha 🀣
function Label3(props) {
const value = props.value
return <div>{value}</div>
};
function Label3(props) {
const value = props.value
return <div>{value}</div>
};
This does in fact not re-render under normal occasions. The reason why the playground worked was not because anything I said, but bc that prop was an accessor.
peerreynders
peerreyndersβ€’7mo ago
yet when a props value is assigned
Stores can help with understanding props. While props aren't necessarily proxies (compilation can optimize them in various ways) it's often helpful to assume that they can suffer some of the same limitations. I like the fact that you have to call signal accessors, it's a very explicit way of communicating that you are retrieving the most up-to-date value. Props (and stores) are a bit more magical given that they rely on the getter/proxy trap to subscribe. That's why destructured props break reactivity.
Without the wrapper around the prop you just get the value that comes out of it the first time it is run. By wrapping it in a function at the call site, it is recognized as a function, so presumably it is executed inside the reactive context which can accept the subscription to future changes.
MDN Web Docs
Proxy - JavaScript | MDN
The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.
peerreynders
peerreyndersβ€’7mo ago
I'm a vanilla purist at heart
Yes, but while learning Solid I'd stay away from the no-build approaches because they introduce some weird edge cases and lead to sub-optimal outcomes. Become familiar with both of these articles And while webpack may have given bundlers a bad name in the SolidJS universe they are a good thing; not the least because of tree-shaking.
DEV Community
Building a Reactive Library from Scratch
In the previous article A Hands-on Introduction to Fine-Grained Reactivity I explain the concepts...
TomDaBomb
TomDaBombOPβ€’7mo ago
Ohhh, @peerreynders, I didn't realize props values were wrapped with a proxy (ie, a trap mechanism) I presume that the proxy calls the solid signal accessor/getter and hence, when one has, props.id, it is the primitive value at the end of the day (not the Solid signal accessor function) I personally like the idea of calling accessor functions directly, too. πŸ‘€ It is a little more verbose, but I too like the feeling of knowing you have the most up-to-date value.
peerreynders
peerreyndersβ€’7mo ago
I didn't realize props values were wrapped with a proxy (ie, a trap mechanism)
Sorry, I wasn't trying to imply that. I was merely demonstrating that in both cases with typeof you are oblivious to the fact that you are dealing with a surrogate structure. Stores are proxy based but people are often surprised when derived properties don't "react" when there are fine-grained changes
TomDaBomb
TomDaBombOPβ€’7mo ago
Haha yeah, it's easy to believe/imagine that it's just magic as a beginner (until you run into an issue and have to learn) πŸ˜†. ^ I come from Vue, and Vue has "computed properties" for derived properties. Just like effects/memos. A bit off topic at this point haha, but one thing I didn't like from Vue (React has it, too) is the "async render queue" (ie, a "promise tick"). As far as I can tell, Solid is sync, which is actually really cool to me!
peerreynders
peerreyndersβ€’7mo ago
As far as I can tell, Solid is sync
With SolidJS 2.0 it's aiming to become more lazy though in order to avoid unnecessary work though it will remain eager where it is necessary. https://youtu.be/sMbICJUGJj4
Ryan Carniato
YouTube
Designing Signals 2.0
Signals are not a new technology. The current wave of Signals and fine-grained rendering has existed in the modern form for the past 7 years. As an early adopter/pioneer in this area I want to talk about where things are and how I see them changing in the next few years. We will take a first look at Solid's work in this area and talk about the t...
TomDaBomb
TomDaBombOPβ€’7mo ago
Also, I'm totally okay with edge cases as long as I understand them πŸ™‚
peerreynders
peerreyndersβ€’7mo ago
Though edge cases can impede with learning the core concepts, so delaying them can be a good idea.
TomDaBomb
TomDaBombOPβ€’7mo ago
(I'm sure they have legit reasons to go async haha)
peerreynders
peerreyndersβ€’7mo ago
I think with this level of attention to detail https://twitter.com/RyanCarniato/status/1736453819566113148 you have little to worry about.
Ryan Carniato (@RyanCarniato) on X
I wonder if I'm the only person who is troubled by the difference between these.
From An unknown user
Twitter
TomDaBomb
TomDaBombOPβ€’7mo ago
Woah, I've actually thought about that, too... I assumed that a promise is seemingly an object/structure wrapping a callback whereas async/await is a context pushed back/deferred directly in the good ol' "JavaScript Event Loop" ^ I could be totally wrong... I just assumed that... πŸ˜†. But, thinking about it is strange nonetheless. Btw, thanks again for the answers/examples/etc, @peerreynders! πŸ€™ It is much appreciated πŸ™‚ Context: I did a bunch of microcontroller programming where we had to write event loops in C (to read sensors and control motors at the "same time"), so JavaScript always feels cozy to me haha ^ Some people bash it (the JavaScript event loop), but I think it's great. You don't have to write one ever haha.
TomDaBomb
TomDaBombOPβ€’7mo ago
Very neat! We called it "events and services" (E&S) for microcontrollers, but the reactor pattern looks like it's for larger distributed systems. Thanks for sharing πŸ™‚. ANSWER: Props wrap Solid signal accessors in proxies and call signal accessors when prop values are being accessed. The result is that you get primitive values when accessing a prop.value. Because they are primitive values (as opposed to signal accessors/getter functions), they must be wrapped with a function when using tagged template literals to be reactive. (For anyone coming here in the future)
Want results from more Discord servers?
Add your server