I appreciate you taking the time to write such a thorough response. I misunderstood your grief with setup + reactivity approach I thought it was due to immutability. I never would associate it with update correctness as once you write things in a derived way you don't tend to have those problems.
You've definitely making me think of scenarios where derived data might take more thought to write. I don't think anyone would want code written like that in the example if avoidable. And when it does happens it happens.
The crux of it is that in some cases it is easier to group around control flow rather than data flow. Data flow is a must for memoization which is why this same refactor occurs today in React if you want to use useMemo. You have the same dilema of where to put count > 0 because you can't conditionally wrap Hooks. In Solid you always write as if the data is derived so you don't refactor to optimize.
The refactor story is add a one liner for somethingElse and move on. Or extract out count() > 0 if you really want to.
The thing is any of the logic (other than early return, although I could show what an early return would map to if you like) you could just hoist into a function or memo. So you could just take your React code and now it's in the pure space. It's like a React component inside a Solid one.
As I said our community is full of trolls. You should recognize the wrapped code in this memo from your useInterval article.
gotta love how easy it is to make a counter work using Solid hooks π
18:13 PM - 25 Jan 2023
Back to the example if you were being lazy you could do this as well but I don't think this is where you'd want to end up:
functionVideoList(props){conststate=createMemo(()=>{// the react component bodyconstcount=props.videos.length;letheading=props.emptyHeading;letsomethingElse=42;// can add something hereif(count>0){constnoun=count>1?'Videos':'Video';heading=count+''+noun;somethingElse=someOtherStuff();// can add something here too}return{heading,somethingElse}});return(<><h1>{state().heading}</h1>
<h2>{state().somethingElse}</h2>
</>
);}
I think the goal of the compiler is really cool. I'm excited to see you guys get there and honestly I'm inspired to go further down this path myself. I wrote about it a while ago but you guys make me want to look at again.
EDIT: The more I look at this though isn't the data derived writing just clearer than the control flow version anyway. It's definitely more declarative feeling and more refactorable. Like let me port my Solid version to one without closures.
This makes me wonder.. if you are going to be smart on re-execution with React Forget are you going to forbade things like console.logs or other side effects in the main component body to prevent people from observing that the code doesn't exactly run top down? The compiler sounds good but I imagine traceability gets even harder.
Yeah, so with your final example we're essentially back to React-land.
I.e. if my entire function body is there, it always re-executes. But I thought Solid was supposed to help me be more granular! :) So we don't get the benefits of Solid here and also the syntax is more clunky (an extra indent level). Especially if aside from heading we're gonna need a bunch of other things. My JSX can't just access those values from the closure scope. Weren't we inside of a function?
If I try to make it granular again by splitting it up, then as you said it starts to look a lot like React with useMemo's. You still have to think about how to regroup things (and whether to split them up or unify them again) each time you change something. I guess it's par for the course in traditional reactive approaches, but after using React this feels like a step back. Why does everything need to be wrapped? Why can't we use the top level function body for its purpose?
That's what we're hoping to solve. Write plain logic, write it at the top level, and let the compiler figure out how to group it. Is this doable? We don't know yet whether our approach is completely solid, but we hope to share an update soon.
Regarding Hooks, note React doesn't place restrictions on your rendering logic. The restriction is on calling React built-in functions. Although in principle, if we were compiling code, we could remove this restriction, I don't know if it makes sense to. Say you want to useState inside an if. What is this supposed to mean? Does it mean this state gets reset whenever the condition flips back to false? Or does it mean that the state persists? It's a bit similar to saying you want to conditionally define a method on a class. Even if you could, it's just not clear what you actually mean by that. The rules of Hooks are restrictive but in most cases I'd say they help do the right thing and have clear semantics (the lifetime is always tied to the surrounding component). And importantly, they don't tell you how to write your code β everything after can have arbitrary control flow.
Right. My last example was just showing the bail out so to speak. This is how a lot React ports look like at first. I just wanted to show you could do things both ways. You return all the fields you need and you sacrifice some granular execution so it works. You could also nest Memos and not lose granularity too.
And yes once you go to useMemo breaking it out we are in the same boat. But what I'm getting at is if you start by writing things as if they are useMemo (whether they memo or not) I'm not sure how much you are bothered by this. I suppose there might be some duplication. I don't think it fundamentally impacts correctness if you are thinking in data. It definitely pushes you towards writing data as being derived at which point being a function or not is sort of whatever.
And like there are other things that you aren't worried about. Because like things like useCallback etc don't exist. Things like memoizing functions are much less common. Like communication between Effects and stable references ...memoizing components, these are all not concepts. Instead you have a model where you feel like you control and know exactly what updates. I'm sure we could pick out more slightly more awkward scenarios for each but to what end.
I don't really agree this is a clear step backwards. But my React experience on the job is much more limited than yours. I wrote and supported a React Native app for 3 years, and only have about 1 year experience doing React on the web(same company), doing a full rewrite of an private social media application (like Instagram for schools). I am not unfamiliar with developing React apps.
When my team moved from Knockout to React Hooks they were really confused. They did a lot of things wrong. They thought they were guarding executions and were getting re-renders. They figured it out but the overall sentiment was, Hooks weren't what I was expecting. They are fine. I thought we'd get some big win here, but maybe we should have just given Solid a try (this was 4 years ago so I didn't recommend we use Solid yet). So to me it is very much a matter of perspective.
Aside I have no issue with Hooks I think their design makes sense. I've never felt Hook rules were restrictive other than not being able to nest them within themselves. I think stale closures are confusing as complexity grows, ie.. if you need to useRef for something other than a DOM node you've hit a point that goes beyond where most comfort is. I only mentioned the rules from the perspective that our useMemo examples would be nearly identical. Unless we are doing some fancy stuff.
Yeah thatβs pretty interesting. I havenβt thought about it from this perspective. To me Hooks are very functional in the classical sense. Theyβre a universe apart from Knockout style, so I was surprised by your mention of trading away the pure model. Hooks do not trade it away, theyβre the clearest representation of that model. But I can see now how superficially they might look like some Knockout-style code. That might explain why people sometimes really struggle with them. Guess thatβs similar to Solid code looking deceptively Reacty β when itβs really not. Thatβs a useful insight.
The examples above are very very simple. They are complete beginner examples, and don't really show where things get either a lot more complex, or way simpler. When you really use both React and Solid, then you'll see which is simpler as app requirements grow.
Here's just one simple example with pure Solid that I challenge anyone to write in pure React without importing additional libraries and with the same simplicity:
This was an interesting challenge, as I could see lots of ways of building this in React depending on which parts of the above code were considered critical.
The big difference with the above Solid code is that this moves the state handling into a top level React component so that React will re-render our components when the state changes.
We also need to wrap the setInterval call in a useEffect in order to kick off the interval from a React component.
You don't technically even need the top level App component or the state...
constOne=({count})=>{return<div>value in One: {count}</div>}constTwo=({count})=>{return<div>value in Two: {count*2}</div>}constroot=React.createRoot(document.getElementById("app"));letcount=0;setInterval(()=>{count++;root.render(<><Onecount={count}/><Twocount={count}/></>,);},500);
This code looks very simple and concise. But this is incorrect code, since the timer running will never stop. It's a time bomb. If you write this code correctly with the timer reset when removing the component, then the code will turn out to be not so concise at all. And I am silent about the fact that this code ticks not 2 times per second, but at unpredictable intervals of time, which introduces a progressive systematic error. To avoid this, you need to measure the time that has elapsed since the timer started.
In $moll there is a special store ticking with a given accuracy for this. Example:
Donβt mind. Iβve worked at Meta and I can count on one hand the number of times I saw people using let in component rendering code (not inside a callback). I always request a refactor during code reviews when I see it. Dan was just using contrived examples to make his point.
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
I appreciate you taking the time to write such a thorough response. I misunderstood your grief with setup + reactivity approach I thought it was due to immutability. I never would associate it with update correctness as once you write things in a derived way you don't tend to have those problems.
You've definitely making me think of scenarios where derived data might take more thought to write. I don't think anyone would want code written like that in the example if avoidable. And when it does happens it happens.
The crux of it is that in some cases it is easier to group around control flow rather than data flow. Data flow is a must for memoization which is why this same refactor occurs today in React if you want to use
useMemo
. You have the same dilema of where to putcount > 0
because you can't conditionally wrap Hooks. In Solid you always write as if the data is derived so you don't refactor to optimize.I'd probably write this and not worry about it:
The refactor story is add a one liner for
somethingElse
and move on. Or extract outcount() > 0
if you really want to.The thing is any of the logic (other than early return, although I could show what an early return would map to if you like) you could just hoist into a function or memo. So you could just take your React code and now it's in the pure space. It's like a React component inside a Solid one.
As I said our community is full of trolls. You should recognize the wrapped code in this memo from your
useInterval
article.Back to the example if you were being lazy you could do this as well but I don't think this is where you'd want to end up:
I think the goal of the compiler is really cool. I'm excited to see you guys get there and honestly I'm inspired to go further down this path myself. I wrote about it a while ago but you guys make me want to look at again.
EDIT: The more I look at this though isn't the data derived writing just clearer than the control flow version anyway. It's definitely more declarative feeling and more refactorable. Like let me port my Solid version to one without closures.
This makes me wonder.. if you are going to be smart on re-execution with React Forget are you going to forbade things like
console.logs
or other side effects in the main component body to prevent people from observing that the code doesn't exactly run top down? The compiler sounds good but I imagine traceability gets even harder.Yeah, so with your final example we're essentially back to React-land.
I.e. if my entire function body is there, it always re-executes. But I thought Solid was supposed to help me be more granular! :) So we don't get the benefits of Solid here and also the syntax is more clunky (an extra indent level). Especially if aside from
heading
we're gonna need a bunch of other things. My JSX can't just access those values from the closure scope. Weren't we inside of a function?If I try to make it granular again by splitting it up, then as you said it starts to look a lot like React with
useMemo
's. You still have to think about how to regroup things (and whether to split them up or unify them again) each time you change something. I guess it's par for the course in traditional reactive approaches, but after using React this feels like a step back. Why does everything need to be wrapped? Why can't we use the top level function body for its purpose?That's what we're hoping to solve. Write plain logic, write it at the top level, and let the compiler figure out how to group it. Is this doable? We don't know yet whether our approach is completely solid, but we hope to share an update soon.
Regarding Hooks, note React doesn't place restrictions on your rendering logic. The restriction is on calling React built-in functions. Although in principle, if we were compiling code, we could remove this restriction, I don't know if it makes sense to. Say you want to
useState
inside anif
. What is this supposed to mean? Does it mean this state gets reset whenever the condition flips back tofalse
? Or does it mean that the state persists? It's a bit similar to saying you want to conditionally define a method on a class. Even if you could, it's just not clear what you actually mean by that. The rules of Hooks are restrictive but in most cases I'd say they help do the right thing and have clear semantics (the lifetime is always tied to the surrounding component). And importantly, they don't tell you how to write your code β everything after can have arbitrary control flow.Right. My last example was just showing the bail out so to speak. This is how a lot React ports look like at first. I just wanted to show you could do things both ways. You return all the fields you need and you sacrifice some granular execution so it works. You could also nest Memos and not lose granularity too.
And yes once you go to useMemo breaking it out we are in the same boat. But what I'm getting at is if you start by writing things as if they are useMemo (whether they memo or not) I'm not sure how much you are bothered by this. I suppose there might be some duplication. I don't think it fundamentally impacts correctness if you are thinking in data. It definitely pushes you towards writing data as being derived at which point being a function or not is sort of whatever.
And like there are other things that you aren't worried about. Because like things like
useCallback
etc don't exist. Things like memoizing functions are much less common. Like communication between Effects and stable references ...memoizing components, these are all not concepts. Instead you have a model where you feel like you control and know exactly what updates. I'm sure we could pick out more slightly more awkward scenarios for each but to what end.I don't really agree this is a clear step backwards. But my React experience on the job is much more limited than yours. I wrote and supported a React Native app for 3 years, and only have about 1 year experience doing React on the web(same company), doing a full rewrite of an private social media application (like Instagram for schools). I am not unfamiliar with developing React apps.
When my team moved from Knockout to React Hooks they were really confused. They did a lot of things wrong. They thought they were guarding executions and were getting re-renders. They figured it out but the overall sentiment was, Hooks weren't what I was expecting. They are fine. I thought we'd get some big win here, but maybe we should have just given Solid a try (this was 4 years ago so I didn't recommend we use Solid yet). So to me it is very much a matter of perspective.
Aside I have no issue with Hooks I think their design makes sense. I've never felt Hook rules were restrictive other than not being able to nest them within themselves. I think stale closures are confusing as complexity grows, ie.. if you need to
useRef
for something other than a DOM node you've hit a point that goes beyond where most comfort is. I only mentioned the rules from the perspective that ouruseMemo
examples would be nearly identical. Unless we are doing some fancy stuff.Yeah thatβs pretty interesting. I havenβt thought about it from this perspective. To me Hooks are very functional in the classical sense. Theyβre a universe apart from Knockout style, so I was surprised by your mention of trading away the pure model. Hooks do not trade it away, theyβre the clearest representation of that model. But I can see now how superficially they might look like some Knockout-style code. That might explain why people sometimes really struggle with them. Guess thatβs similar to Solid code looking deceptively Reacty β when itβs really not. Thatβs a useful insight.
The examples above are very very simple. They are complete beginner examples, and don't really show where things get either a lot more complex, or way simpler. When you really use both React and Solid, then you'll see which is simpler as app requirements grow.
Here's just one simple example with pure Solid that I challenge anyone to write in pure React without importing additional libraries and with the same simplicity:
Solid playground example
Example on CodePen with no build tools:
This was an interesting challenge, as I could see lots of ways of building this in React depending on which parts of the above code were considered critical.
The most natural way I would write it is:
codepen.io/karlokeeffe/pen/vYzxPEX
The big difference with the above Solid code is that this moves the state handling into a top level React component so that React will re-render our components when the state changes.
We also need to wrap the
setInterval
call in auseEffect
in order to kick off the interval from a React component.You don't technically even need the top level App component or the state...
This thread is crazy.
Impure solidjs components being rebuilt using pure react components.
Let's turn this into a different challenge: write the code and the tests for each of these in solid and react
This code looks very simple and concise. But this is incorrect code, since the timer running will never stop. It's a time bomb. If you write this code correctly with the timer reset when removing the component, then the code will turn out to be not so concise at all. And I am silent about the fact that this code ticks not 2 times per second, but at unpredictable intervals of time, which introduces a progressive systematic error. To avoid this, you need to measure the time that has elapsed since the timer started.
In $moll there is a special store ticking with a given accuracy for this. Example:
Donβt mind. Iβve worked at Meta and I can count on one hand the number of times I saw people using let in component rendering code (not inside a callback). I always request a refactor during code reviews when I see it. Dan was just using contrived examples to make his point.