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
Change it to
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.
Clarification: I was referring to line 25. It is unwrapped yet is still reactive. (updated my question)
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.
It's re-executing the component, which also causes all the html-elements to be created again on each prop change.
it would be the same as
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
I was referring to line 25I 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
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:
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
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
with () => count
you somehow managed to deliver a function via props.value
which by the rules hypothesized would be run.
If you would have followed the instructions by the letter you should have written
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).It's re-executing the componentWhat am I missing? https://playground.solidjs.com/anonymous/bf76951c-7041-4886-a775-8f23cf186125
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
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.
I'm not intimately familiar with the source code; I just wondered if I needed to adjust my mental model.
no i think u were spot on!
"Blind chicken" and all that π
@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!
if props.id is a getter, isn't a getter (to a primitive value) still a function?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.
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.
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 π€£ 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.
yet when a props value is assignedStores 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.
I'm a vanilla purist at heartYes, 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...
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.
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 changesHaha 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!
As far as I can tell, Solid is syncWith 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...
Also, I'm totally okay with edge cases as long as I understand them π
Though edge cases can impede with learning the core concepts, so delaying them can be a good idea.
(I'm sure they have legit reasons to go async haha)
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.
Twitter
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.
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)