This article hits on something I've felt for a long time. The idea that "hooks are superior" to me is ridiculous. If a linter is required to tell me when I'm writing a bug that is not immediately obvious, that is a failing in the framework to round those edges. Lints are not rounded edges!
Solid is nice and _seems_ to fix the issues with hooks, but as another comment mentioned, the challenge is with building at scale. It's unclear to me how this scales and where the sharp edges are.
React IMO trades off performance in exchange for ergonomics. These ergonomics bear fruit early on, but you start dealing with this debt very quickly. As "nice" as Redux is, I shouldn't need it so early. React is designed in a way that results in pretty terrible performance. I once wrote a React app, and discovered perf issues 2 weeks in. Any UI framework I used in the past would have scaled beyond this point without having to have the data layer rewritten. Frameworks of the past also had what seemed like way less "magic".
I totally accept that it's seriously nice to write, but how many trees has React alone burnt?
React traded one set of footguns for another set. This is also after they fudged with the class component lifecycle methods before tossing in the towel on that entire API.
> If a linter is required to tell me when I'm writing a bug that is not immediately obvious, that is a failing in the framework
yep. They also commandeered the entire use* namespace just so they could get lint to warn when you put a hook outside the start of a function. Hacks on top of hacks.
I have the same complaint about hooks. Most people seem to ignore that tidbit, but to me it's really frustrating. Plus I recently hit more hook issues when putting a setInterval inside a useEffect. There's no way to do a normal didMount/willUnmount workflow without other hacks (i.e. useRef) just to set up a simple timer. Maddening!
Edit: after writing this I went and read the article. Same scenario I was bitching about lol
People always use the setInterval() issue as a footgun. The real footgun is people not reading the documentation for the framework they use, because the exact example is handled in the official react docs [0]. I always advice aspiring react devs to understand why this exact setInterval() doesn't work (and why there is the need for the rule-of-hooks), because it will automatically create an understanding how hooks and react itself works.
I think I ran into a very similar issue recently too, stale closures right? didMount/willUnmount worked perfectly, but useEffect got screwy very fast, even posted a SO article trying to see the best workaround, nothing great.
My favorite accomplishment with react after not using for some time, was to set a conditional timer for app level event using hooks for a set of tiered conditions which should be ignored unless it passed a threshold.
Took me at half a week at least and I felt like a god after. Then I realized this was all phenomenal waste of time and I decided my next job was going to be cloud and backend focused. Only in the js world can you realize immediately as soon as your done that it's effectively worthless programming hassle.
React hooks is another attempt to gain ergonomics. The idea is to try to spread the virtual DOM into native effects. In theory the code specifies or declared the effects once and the framework takes care about subscribing/unsubscribing as necessary. But in practice this became so messy that in any complex cases one better stick to classes and explicit subscribe/unsubscribe.
The right way to do that exists in Elm. But I do not think there is a way to translate that into JS without requiring either a lot of boilerplate or very messy code
I agree that hooks "can" be messy but in my experience they are quite useful and nice to maintain. That is if the code is written with maintainability in mind - which applies to any code really.
I've been working on a quite complex React codebase for 5 years now - it's not the biggest but still not so small. I was there on day 1. The team that works on it now full time is ~15 devs + 10-15 doing some minor stuff from time to time.
We've started using hooks about 1 year after they introduced them. Before we were very deep into redux and redux-saga. Now most of our codebase uses only hooks for most of the things we did with redux before - there are still some redux but most major parts are migrated to using hooks and contexts. It is probably the best decision we made after switching to TS. Most of our codebase is still very easy to maintain. Last year I went on a paternity leave for 6 months and I was pleasantly surprised how easy it was to adapt back.
Edit: forgot to write about Elm. I like elm a lot -even though I don't agree with how it's managed-. I even organized a local elm meetup :) But I think while it is very good in theory, it's not too easy to scale especially if you plan on hiring more devs. Also the way effects and data is completely separated from the view is not very ideal for bigger projects. You either end up having to touch too many files for a single feature or have incredibly big files.
Oh that's jumping the gun. TEA doesn’t compose and you can pretend you don't need components until you do and then you end up with big mess of hard-to-follow spaghetti code. It's not 'bad', and has good use cases but The Elm Architecture is not without flaws.
React would work well as a compiled to metal language. Instead we end up with React on Typescript on ES6 on ES5 on the browser that still only has one language targetable. We're shackled to a rocketship in motion desperately monkey patching the engines.
Back when they were initially released my teammate started rewriting everything in them. Little did he know, if you're not careful with hooks, you can severely damage performance.
My take is that if a feature requires a linter to be properly implemented then it's obviously giving the developer too much rope to hang themselves with.
Also it appears that they were introduced mainly for performance, but for some reason sold as the new "better" way of writing applications.
I actively avoid React and its ecosystem, because I believe there's just too much FOMO and "fashion"(for lack of a better word) in what drives its development and not enough meritocracy.
Read the implementation of Hooks (in the React codebase) if you want a big LOL. They're a slower, partial re-implementation of objects with bizarre custom syntax. In a language that's already so heavily OO that the functions are objects.
>If a linter is required to tell me when I'm writing a bug that is not immediately obvious, that is a failing in the framework to round those edges. Lints are not rounded edges!
Those linter warnings with React hooks show just how much of a disaster it is. All of the "simplicity" that comes with hooks is just complexity that is now completely out of sight, and only appears once the code is run in a linter. That is even worse than having the complexity in the code.
After working with React for years it is something I would not recommend and can only think of limited use cases to use.
You can write really performant software in React, but ergonomic (beyond stockholm syndrome) I would not call it.
It's also difficult to implement quality software engineering principles in a React application such that an application is maintainable, glacable and easy for someone new to a project to pick up.
I don’t understand why people think react hooks are meant to be ‘functional’.
They are highly, deliberately, emphatically procedural.
The order they are called matters! The time of when they are called matters! The number of times they are called matters! You get a different result from calling the same hook with the same arguments at different times! You get different results if the call site for them is in different places!
They do enable you to do some things in JavaScript that are more easily enabled with higher ordered function stuff in functional languages, but there is nothing ‘functional’ about how hooks work or how they are used.
And that’s okay! ‘Functional’ doesn’t automatically mean ‘better’ and ‘procedural’ doesn’t mean ‘worse’. For what they do, hooks (considered as the entire hooks ecosystem including linting extensions that help enforce the rules as if they were syntax errors) are a very clever extension within JavaScript syntax that let you pull off some very neat separation of concerns in a reactive code model.
‘Functionalness’ has very little to do with whether hooks are good or bad.
>"If a linter is required to tell me when I'm writing a bug that is not immediately obvious, that is a failing in the framework to round those edges".
I do not want this handled at the framework level though. There are plenty of times in my day-to-day where that linter is wrong. If I were to add one of the dependencies (it's sure I should add) my component would re-render over and over. Sure, one could argue, "well, then you've poorly architected your code"... but THAT is where I feel like the failure occurs. Having to do write my code to please a paradigm. What that means is I only "sorta agree" with the design trade-offs. And much like Ruby on Rails, you either go all in, or spend your days hating the framework.
I do worry that Solid also has these "If you know, you know" edges though. So I'm trading looking at a function and assuming it'll run over and over with, looking at a function and thinking, "how can I make this thing run again when it needs to?" I'm not saying it's wrong, it's just another piece of tacit knowledge one has to learn when adopting a new framework.
With Class components it was often easier to create Pure components, or do a shouldcomponent update check to optimize performace. They later introduced memo for functional components.
> If a linter is required to tell me when I'm writing a bug that is not immediately obvious, that is a failing in the framework to round those edges.
Isn't the point of static analysis (including linters) that they catch those kind of bugs? Having that little red underline (or console warning) saves a lot of pain.
Hooks have completely changed the way I write React code. It's so much easier to test and reason about. Really the only problem is that the guardrails aren't more heavily enforced.
I don't think complaining about needing a linter makes sense. JSX is already a compiled language, so you have a compile and build step (doubly so if you're also using Typescript). Think of ESLint rules-of-hooks as just a plugin to your compiler that further modifies the syntax of JavaScript to include rules about when and how you can call certain functions.
Hooks are a syntactic element of react code. Calls to them aren't legal inside conditionals or loops.
Full linting support smooths over that rough edge just as effectively as babel handling ?. syntax smooths over its absence in certain JS runtimes.
But those dependencies would be static. This goes beyond that. I won't call hooks flawed, they are suitable for React's model, but looking at what reactivity does is a different sort of thing. It is a subtle difference at first.
New JS frameworks always make for compelling hello world examples.
Can you branch on state or use loops over data in Solid.js? The reason _why_ React has a virtual DOM is to enable more interesting relationships between your data and your presentation. Anyone can make a framework that makes the source code for an incrementing number look pretty!
As an example of this point, check out the "Simple Todos" example for Solid.js[1].
In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.
I've been writing React and React-alike code for a long time. I think that fine-grained updates avoiding reconciliation are a good idea, especially for performance. At one point, I built a React-like library for Roblox and Lua whose most novel feature ended up being "Bindings"[2], which look sorta like Solid.js state containers. They create little hot-path data dependencies, but the bulk of your components still use normal React-like rendering.
>In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.
I find this a totally bizarre complaint. I've spent the past few months working on Svelte stuff and I've seen people on HN make this same complaint about Svelte's templating language with {#if} and {#each}. Who cares? What is so wrong, exactly, with "reinventing a concept that's already in the language"? It does not make code any harder to understand or to write, and it does not harm performance (in this case, quite the opposite).
I would much rather have a reactivity model where I plug in completely standard concepts and patterns (a for loop) than one where I have to deal with a bunch of framework-specific, complicated ones (hooks). That Solid's reactivity primitives are familiar is an advantage, not a disadvantage.
Because you sometimes want to filter, sort or project your data. Then you have to handle this in viewmodels or invent more and more features for the templating language. Then you want to refactor into components. So you need facilities for invoking subcomponents. Maybe you want something recursive to display tree-like data.
So you end up with a secondary full featured language usually with worse IDE support, worse error messages, more surprising issues, etc. You need to understand the scoping mecanisms and if things go wrong hope there is a debug tool available.
And in the end those templating languages do not prevent you from mixing UI responsibilities from the rest of your code.
If you want a reactive model you can have one. I personally prefer explicit messages like calling setState.
A react feature that I appreciate is that it is "just javascript". It's easier to learn how to loop or have conditionals in React because it uses native JS features. It makes it easier to understand, for me.
Having templating DSLs in other frameworks isn't a deal breaker, but it's a pro of React that I appreciate.
I've not used Svelte, but when I've used such DSLs the problem tends to be that they're not very flexible, and as soon as you step outside of the provided helpers you're stuck and you just can't do the thing.
> What is so wrong, exactly, with "reinventing a concept that's already in the language"?
Nothing, inherently. Just like there's nothing inherently wrong with having extremely clear and simple rules for how to use hooks, and lint rules to identify when you're not following those rules. Nothing inherently wrong with either, some people just have strong distaste for one or the other.
> In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.
Once you deal with larger amounts of data and need virtualised rather than fully-materialised lists, you start using different things in React as well. The fact of the matter is that if you care about performance at all, the simple ways are just insufficient, and the native language constructs were designed for procedural programming, not reactive interface rendering, which requires fundamentally incompatible semantics. It’s not even fair to claim that React uses regular JavaScript idioms—VDOM, hooks, the entire shebang is all about eschewing regular JavaScript idioms because they don’t scale. (OK, so there’s also the matter of transient state like scroll positions, element focus, and form field values; it’s not fair to say that React does all these things purely for performance’s sake, as the naive immediate mode approach would also break such functionality.)
> virtualised rather than fully-materialised lists
I want to push back somewhat on this practice. Our computers are fast enough now, and the browser implementations optimized enough, that they should be able to handle thousands of materialized list items without breaking a sweat. Sometimes you really need virtualization, e.g. if the underlying data source has millions of records. But if the data can be fully materialized, then the implementation is simpler, and the user can take advantage of things like find in page. Virtualization is a convenient way to avoid the inefficiency of unoptimized VDOM-based rendering (e.g. with React, and yes, I know there are other optimizations available in React), but fine-grained updating (as in Solid) is even better.
What JS framework would you choose to work with big datasets like, e.g. a data grid with half a million rows that should have a "filter as you type" functionality?
>…much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.
I'm right there with you, but when React invents a whole markup language inside of JavaScript, it's not in much of a standing to make purity criticisms.
Well you've obviously completely missed the point of JSX then. The "whole markup language" that they invented is literally a line for line transform. Optimisation aside, there's no reason why line 58 of a file with JSX in it won't be line 58 of the transpiled JS file, and read exactly the same. All JSX is is a custom function call syntax.
It's about as pure as you can get while having any sort of html-ish 'templating' whatsoever.
So, I'd say it's exactly in the right place to be making purity criticisms. They've taken the only approach that preserves the integrity of the code and doesn't involve build time magic.
That is an utterly bizarre rejoinder. JSX has always been (and afaik still is) optional and extremely thin syntactic sugar. It’s little more than a convenience macro (so that views can look a little more like the markup equivalent).
It has no intrinsic semantics, and maps pretty much directly to actual javascript (which you can write directly or use an alternative helper for — hyperscript being a common one).
> In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.
This argument is silly because it inevitably becomes a pissing contest of who can be most like vanilla JavaScript. In that case, why use JSX? Just write hyperscript calls instead. Why use React Router/React Context helpers? Just wrap your components using vanilla function providers instead. Why use React Hooks, which inevitably look like magic to a JavaScript veteran because the library inherently hides away some global state? I hope you can see what I'm getting at here.
> In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.
I had/have your bias, but from playing with it I found a couple things:
1) Like React, you can swap out the template feature for a function call (or subcomponent).
e.g. instead of
function displayTODOs<T>(todos: T[]): any {
let arr: any[] = [];
for(let [i, todo] of todos.entries()) {
const { done, title } = todo;
let elem = (/\* JSX \*/);
arr.push(elem);
}
return arr;
}
...
return (
<button ...>
</button>
{displayTODOs(state.todos)}
);
2) Even with my bias, I must admit I found the `<For...` syntax to be surprisingly easy to read and fast to eye-parse; much more so than other 'templating' (using your term) languages/macros/syntax I've used over the years.
Likewise I simply don't empathise with the author's complaints. Hooks make sense if you think in closures. Hooks are isolated so you can think about them in isolation.
What I like about the React monoculture is that it's one less thing I have to care about. I can focus on the other aspects of my programs, beyond turning JSON into HTML.
I haven't used SolidJS so I'm not going to put it on blast. However, hearing people compare its reactivity model to Knockout JS gives me the heebie-jeebies, because Knockout projects were horrific to reason about (and test) beyond a certain scale.
For reactive control flow to be performant, we have to control how elements are created. For example, with lists, a simple map is inefficient as it always maps the entire array.
>The reason _why_ React has a virtual DOM is to enable more interesting relationships between your data and your presentation. Anyone can make a framework that makes the source code for an incrementing number look pretty!
Actually, that is only half the reason. You can do whatever you want in my library (github.com/thebinarysearchtree/artwork) and it doesn't have any kind of virtual DOM or whatever Lit does, because you just create elements with JavaScript. The second reason, that everyone just assumes is the default, is that React has to use HTML-like templates and not just JavaScript.
I've used React for ~3 years, primarily with function components and hooks. I think that hooks were a wonderful addition and I think the framework has made smart choices with checking object equality to decided if components re-render.
That said, I think that easily the most difficult aspects of react revolve around how re-renders are triggered. Maintaining referential equality to stop unnecessary renders gets tricky when you are passing functions or objects. Suddenly you need to be using `useMemo` and `useCallback` and passing dependency lists that all have to be primitive values unless you want to memoize them as well. It can become such a headache that the official line around it mostly seems to be "make the render fast, don't worry about unnecessary re-renders" – good advice, until you hit a use-case where you need to worry.
Solid takes these problems and just vanishes them. UI state knows what its dependencies are automatically and only updates when they change – even in a sub-component level!
To be fair, I've never used Solid in anger, and moving to it would be a big ask when there is such a good ecosystem built up around react. That said it is easily one of the most exciting projects on my radar, and the developer Ryan Carniato seems extremely knowledgeable in the area.
Speaking as a british programmer, if a given piece of software hasn't made me yell expletives at the screen at least once, I probably haven't done something sufficiently non-trivial with it to count as "used in production" yet.
But then again even by british standards I am unusually sweary when writing code.
Had to explain "used in anger" to my German gf last week, as her boss had used it and she was confused about why he was angry with her code. It's surprisingly hard to explain the nuances of it.
Weird. I'm not British by I say "used xyz in anger" because I read lots of other programmers saying it. Had no idea it was regional, thought it was hacker lingo like "grok".
> Solid takes these problems and just vanishes them. UI state knows what its dependencies are automatically and only updates when they change – even in a sub-component level!
I haven't looked at Solid but I've used MobX extensively, and this sounds a whole lot like it. It integrates well with React, so you might give it a look if you've got an existing React codebase.
Agreed, after 6 years of use, my gripes with React come down to the re-rendering and lack of syntactic sugar for commonly used things. Hooks can get messy, but complicated components were complicated even with the class syntax.
What React has going for it, is that it is predictable. That is an extremely important part of any tool.
"UI state knows what its dependencies are automatically and only updates when they change" - you should check out [valtio](https://github.com/pmndrs/valtio).
There are a lot of React state libraries that do similar things with Proxies. I think the part that is not as emphasized is how that reactivity extends to the view. Instead of re-rendering components it uses that knowledge to directly update portions of the DOM. So while MobX, Valtio, Jotai, Recoil etc localize change in React, they still feed into the whole VDOM React cycle, instead of just updating exactly what changes. It's not a characteristic of these libraries but the fact they feed into React.
> "Ohhh, an OO pattern with a couple of one-liner lifecycle methods is just WAY too much code! Higher likelihood for errors and worse developer experience."
...
> "So instead, I'm going to replace this with a functional pattern, that crams a couple of lifecycle functions into a closure, and is riddled with edge cases and common developer mistakes."
This article perfectly crystalizes why my career has tracked toward the backend over the past decade. All of the virtual ink in this article, and honestly most of the complexity in the field overall... and it seems to really all just boil down to, "I think this looks cooler."
I've been in the industry since the days of VB6 (which I loved). I've never been more productive than I was with VB6 and WinForms. That said, UI has always been a mess, even then. The more feature-rich you want your UI, the gnarlier and messier it gets.
Everyone always likes to blame the front-end engineers for being slovenly, but my experience is that; UI is just messy. It's just hard to program cleanly.
Not to mention, modern UIs are generally more complex than the old VB days (at least, my programs are): they have to adjust to various screen sizes. They are almost always client/server apps and need to account for more failure modes. They are built on top of a mess of a document layer that was never intended to be an application layer.
This last point is the reason the front-end space has so much churn. It's still an unsolved problem, and may never be well-solved, but I'm glad folks are trying to solve it. React was a step-change for me in terms of building better UIs. I'm looking forward to the next step-change.
Lastly, before someone makes the argument: no; we aren't going to stop building applications on top of the DOM + CSS. Not until someone comes up with an alternative that gives us the same ease of distribution and broad base adoption.
Edit: I should note, I'm a full-stack dev, and have been more back-end than front-end for most of the early part of my career. The backend is always easier for me at least, but I don't think it's because front-end devs are hipsters.
> UI is just messy. It's just hard to program cleanly.
I'm an almost entirely frontend dev (iOS + macOS now, web development in the past) and this is what I've come to believe. It's easy to program UI cleanly if it's extremely basic or not very usable — but the second you want an animated transition between states, or to remember what the user checked on the last page so that you can keep it checked on this page, or any number of other things that you need to do if you actually want people to _enjoy using your software_, it gets much harder.
My take is it's because building UI is building software for humans, and humans often want behavior that doesn't allow for clean abstraction. Backend dev is more about building for other software -- not that it's easier, just a different set of problems.
I have been in the industry since punch cards were a thing and servers took up multiple rooms. I've never been more productive. UI has gotten a lot better since those days and I have migrated my efforts to use frameworks such as Vue.js which is sofar the most elegant and modern approach for doing UI work. Creating a desktop app is very easy with electron js although beit a bit heavy and not the most effecient in terms of memory, cpu cycles, etc but it's easier than punch card days I used to enjoy.
I've also been doing both sides since the 90s and it's funny how people speak of "just" frontend coding. It has lots of async/parallel issues with messy constraints, and it's the dumping ground for dirty data and error handling issues. It's often done poorly, but doing it right is a very complex problem.
Back when I studying CS and as Junior, I would laugh at memes about "Frontend vs Backend" where they described the Frontend as this pretty pasture and the Backend as this god forsaken place of spaghetti code.
In a way I find this meme even funnier now because of how backwards it is. Often times it's the backend that's really clean and organized and the frontend is a hot mess.
VB6 was great. I also really enjoyed RealBasic for desktop apps on the Mac. It just worked. Network programming with sockets was so simple. Now I open XCode and am completely overwhelmed.
The OO pattern is far worse, and it's easy to show why: there is only one lifecycle method of a type for each class. This means that when you have a set of related behaviors, you have to split them up across several lifecycle methods and keep them in sync. Sure, that's fine if you just have "one-liners", but software grows, and over time your class will become difficult to maintain as each of those lifecycle methods start managing several different behaviors, all interrelated. With hooks, this isn't a problem: each set of behaviors can be encapsulated by a single hook that handles all lifecycle events together. This can be further abstracted into its own function, which allows them to be trivially reused in any other component. It's simply not feasible in OO patterns.
I think if you actually used hooks, you'll notice this immediately. It's not a matter of "looking cooler", a lot of language theory went into making UI development functional and less stateful, which in turn makes it far easier to manage complex applications. A small team can maintain a pretty huge beast if using hooks effectively.
> All of the virtual ink in this article, and honestly most of the complexity in the field overall... and it seems to really all just boil down to, 'I think this looks cooler.'
IMO there's significant complexity in building a feature-rich frontend client. The "thicker" the client, the worse it gets. There's definitely a lot of 'I think this looks cooler' going around, but also we shouldn't forget that the need to come up with something better is partially a response to very real, very-not-imagined, frontend complexity.
> IMO there's significant complexity in building a feature-rich frontend client.
Doing complex things is always complex, but when SIMPLE things are complex, then something is going very wrong.
And this is exactly where we are with most JS frameworks these days. Layer upon Layer of abstraction, and instead of the complexity the dev has to deal with decreasing as a result of it being abstracted into frameworks offering simple interfaces, complexity increases.
You're using this straw man to demean the author, but I think you miss an interesting point: it can be very hard in software developer to articulate the good and bad. "Uncle Bob" refers to this as "code smells", where you can't quite say immediately what's wrong here, just that you don't like it much. Something smells bad.
And maybe the point of Solid is to point out a mistake React made in their implementation of hooks. Hooks were badly needed, no doubt, but often times an okay solution to a really serious problem can look like a great solution.
The back end went through a lot of this already. That's why the patterns are more stable there, and why you feel less churn. The front end is exciting precisely because there's so much to still figure out!
To people who know what they are doing more code than necessary to accomplish a task is the code smell.
To people who are super insecure vanity code is required because patterns are memorized, so any deviation from the unnecessary boilerplate is the code smell.
I didn't find the word "think" in the article. Did you mean "feels like this looks cooler"? Feeling does appear to be the prevailing way decisions are made and put forward in the community. I think that's a problem. And one that won't be going away anytime soon.
UI developers focus on user interface, and much of that has to do with looks, style, and personal ergonomics. In a way, it makes sense that the engineers steeped in this type of problem set would be oriented to thinking this way. Even if they aren't initially, the probably will be over time.
UI development seems determined to repeat every mistake we've made on the backend over the past thirty years, while adamantly refusing to ask us about any of those mistakes.
I don’t think there’s much overlap between the two, besides general programming best practices.
In my experience, backend development is often simpler because you can move any state out of your own code into dedicated external components, like databases and queues. With frontend code, you have to manage state; there’s nowhere else for it to go.
True but backends are not always refuge. The back end Java version at my company would have a DI container, a few interfaces, factories, configuration files, custom attributes and a few random library dependencies thrown in.
A factor of two reduction in LOC and an elimination of almost all boilerplate is justifiable in itself. I don't know where the author said "I think this looks cooler"
I swear we're just going around in circles because people only have a surface level understanding of these front-end frameworks, and the challenges with building at scale.
react isn't about 'hooks', 'jsx', 'top-down-state', or 'component-driven architecture'. All these frameworks are component-based, can have top down state only (or do bottom up in react), can use things like jsx/hooks because it's just syntactic sugar (vue has jsx support).
react is fundamentally about 'inputs changed, render this'. This got rid of a lot of issues with poor code because frameworks had crappy DX (angluar 1 scope nonsense, and overengineered DI concepts), and people were bad at tracking side effects because a lot of people just wanted a search bar with some cool features and not everyone was building sophisticated products.
That setInterval example is fundamentally against what react is, and is basically svelte/vue/(react + mobx).
> react is fundamentally about 'inputs changed, render this'
I mean, pretty much all frameworks these days have that fundamental declarative model, react wasn't particularly innovative on that front (e.g. the declarative model already existed in angular, knockout, etc)
What the setInterval example highlights is that newer subsystems in React like useEffect and Suspense are bolted on top of earlier iterations that weren't originally designed to support these kinds of semantics, and the dissonance between API design iterations has become noticeable. This is a pain point that is relatively unique to React.
The growing popularity of Svelte and Solid are largely because their API designs align naturally with how people expect features to work, without people falling into pits of failure like stale closures and incorrectly wired dependencies. React is popular and it puts bread on your table and all, but pretending it doesn't have warts doesn't do anybody any favors.
> react wasn't particularly innovative on that front (e.g. the declarative model already existed in angular, knockout, etc)
React was absolutely a breath of fresh air when it was released.
Knockout was similar to Solid.js in that they both have functions that you call which then log a data dependency, then when the data changes the UI updates. This led to lots of pain, because instead of a plain value, you have functions which return values, and you need to be very careful about when those functions are called, otherwise the data dependency might not be tracked properly.
Angular had a similar issue, as its state-based observation relied on special scopes. Updates in the wrong scope could be lost or delayed.
React’s approach of only diffing the rendered UI rather than trying to drive updates based on diffs of the input data was vastly simpler, it was much easier to understand the data flow through explicit state and props.
This is just iteration on the awesome groundwork that React laid, and shows that things can still be much better. React has some very peculiar patterns that don't really jive well with javascript as a language, or its ecosystem. If the setInterval example is fundamentally against what React is, then IMO that really hammers the point the author is making. I have a few React projects under my belt, and often times still find myself confused by hooks or the mess that I create when I use them.
In my personal opinion when there is a lot of complicated state in a component and there is no real benefit to splitting it up into smaller components then hooks are inferior to the older lifecycle methods (in creating understandable, maintainable code), however in my experience whenever I come to place nowadays this would be considered heresy and everything needs to be in hooks even if you have 10+ and growing number of hooks to make everything hang together.
The thing is that While react is against side effects, javascript is not. Which result in these impedance mismatch where what devs want is against react itself.
Vue/svelte/solid do not fight against js, hence they do not end up in similar situation
React lets you build DOM with normal JavaScript loops and .map; solid requires its own For element. How do you define “does not fight against js”? Because the above feels likes solid fighting against js.
i'm finding a hard time articulating what you said, if React is against side effects then useEffect wouldn't have existed and we wouldn't have data fetching.
React is unique in that everything in the component is within the render path, while the rest of the frameworks (that you've mentioned) doesn't.
you might be mistaking "side effects during render is bad" for "side effects is bad", the two statements are not the same.
> All these frameworks are component-based, can have top down state only (or do bottom up in react), can use things like jsx/hooks because it's just syntactic sugar (vue has jsx support).
Honestly, the boon of React is just how easy it is to create components, or at least how simple things were back in the day - it is exceedingly composable, moreso than AngularJS, Angular or Vue have been, at least in my experience. In React, your component can fit within a single file, containing simple syntax, especially for when you're making a pure functional component with no side effects or hooks. And even when you need to add something more complicated, you just have a method or two to change, essentially "progressive enhancement" for your code.
Though admittedly state management, or at least our current approaches to it ruin everything with endless boilerplate (Redux, Vuex etc.) to address an issue that may or may not be easier to represent, though some libraries certainly try (MobX comes to mind).
Of course, my experience leads me to agree with the article, in how React in combination of hooks sometimes is problematic, although in my case that was primarily because of render loops and how the stack traces are akin to JDK 8 NullPointerExceptions, where you couldn't see exactly what's causing you problems: https://blog.kronis.dev/everything%20is%20broken/modern-reac...
I'm probably wrong in liking class based components since those have other issues and Vue/Angular both feel a bit less productive in comparison, even if sometimes easier to reason about, with different tradeoffs to them. Maybe i should check out Svelte some day, but i guess it's all just one long slog of finding what works for you and what doesn't, much like it is with back end programming languages or even relational DBMSes.
Zustand has solved the complexity of state management for me, although getting {employer} to adopt it is of course a different problem altogether.
It's really bizarre to me how poorly useContext works, in contrast to how good everything else in React is for the most part. Having a good, "official" global state management solution that requires little boilerplate would be a huge benefit.
> In React, your component can fit within a single file
Furthermore, a lambda local to a function can be a full-blown component (visible in DevTools etc.).
So if you need a component that is used in only one other component, you can neatly encapsulate it and make it invisible from the outside, which can be useful on occasion.
React was about bringing a more immediate mode UI programming model to the web, as opposed to retained UI programming models are a PITA to work with. Retained doesn’t work well in games, it doesn’t work for productive UIs either.
That's not completely correct. React is, and has been from the start, a UI rendering library. Its fundamentals are component abstractions, the component tree, lifecycle, the virtual DOM. We had other declarative frameworks before React (Ractive.js, svelte's spiritual grandfather, being the most popular one).
The 'inputs changed, render this' paradigm (and by this I mean reactivity, not the declarative model) has been around for much longer and is exactly what this post is about - React doesn't really do that, since it relies on you to explicitly tell it, via dependency arrays or setState calls, when to re-render. It is not fundamentally different from `.on('change', this.render)` code we were writing back in 2010, just a lot of syntax sugar on top.
That React managed to sell itself so well, while not actually delivering on the reactivity or performance promises, is the surprising part. I'm excited for the future as we finally move on from this era.
I keep my sanity by ignoring all of them, focusing on mastering pure Web standards only, and delving into such frameworks only when I am required to collaborate with Web FE devs.
I recently started to look at native web components (custom elements), at first I thought I could replace {insert your framework here} with it, but it seems that it doesn't solve the issue, you still need some kind of framework built on top of it to achieve the same goal.
If you have some recommendations or want to share your experience with working only with web standards I want to read them :)
This is about "feels like" and me too--the little newsletter popup and lack of substance shows this. React became obsolete over the past few years as browsers increasingly adopted the mix of web components features (eg componentDidMount vs connectedCallback). Just using React, and the explanations for why a technology is and isn't used in an organization speaks to the level of practical knowledge and detritus in projects. That products survive this long with React illuminates how resistant the community is to... reading and doing work in the easiest, pragmatic way possible. Like why bother writing an article like this?
That is a big call regarding web components. I did some looking into web components for a recent project and React is just way ahead in many areas. You could use a framework like Lit to help smooth things over, but that just speaks to the underlying standard being somewhat cumbersome to use. I totally want web components to succeed and be the new way of doing things, but I get the feeling that is still a while away.
I've been maintaining a web component library with Lit for a while. Web components overall don't feel ready for primetime. Just making a custom input field and have it work with a <form> is chore.
Slightly dismissive, but I agree the setInterval function was a little misleadingly contrived.
The transition to functional components was to reduce the coupling between abstract functionality and DOM-related lifecycle events.
React hooks are mostly about expressing where and when you want to memoize a value, with the default being not to.
Once you learn what to look out for, and properly designing and review codebases at scale, these trivial issues don't happen all that often. Additionally, being able to specify memoization parameters explicitly brings extra flexibility and some additional design patterns.
The original angular worked perfectly for me, but that's because I loaded the source code onto my tablet and read the entire thing top to bottom over a few evenings in a pub beer garden with a cigarette and a pint to keep me company.
Newer frameworks are absolutely better for anybody who isn't sufficiently batshit enough to do that, but much though I enjoy react + mobx (especially react + mobx-state-tree) I've never got to the "I have the core source code in my head and can mentally dry run it as a desk check type operation when debugging" stage with them like I did with early angular so - with the level of jank inherent in its scope nonsense entirely acknowledged - I still occasionally miss it even so.
(this is mostly me being nostalgic, I think, the newer stuff is absolutely better but that was a fun few evenings and for its era damn but I could make that thing sing)
I think for progress you need to look outside the js world. Fundamentally, people keep repeating the same mistakes there.
I'm having a lot of fun lately using Kotlin-js for example. We use the Fritz2 framework, koin for dependency injection (popular on Android as well for good reasons), and fritz2 relies on kotlin's co-routines and StateFlow for state management. It makes for a surprisingly concise code base. For example, the counter example from the article with that would look something like this:
class CounterStore : RootStore<Int>(0) {
val koinCtx by lazy { GlobalContext.get() }
// handler that you can bind events to or invoke directly like below
val inc = handle { old -> old + 1 }
init {
// launch co-routine to keep on incrementing the counter
GlobalScope.launch {
while (true) {
inc()
delay(1000)
}
}
}
}
val koinCtx by lazy { GlobalContext.get() }
fun RenderContext.counterComponent() {
val counter by koinCtx.inject<CounterStore>()
h1 { +"A Counter" }
// react to changes in the counter
counter.data.render { currentCount ->
p {
+"Current count: $currentCount"
}
}
pushButton {
icon { arrowUp }
events {
clicks handledBy counter.inc
}
}
}
fun main() {
startKoin {
modules(
module {
single {
CounterStore()
}
})
}
render("#target") {
counterComponent()
}
}
There's a lot going on here that I can't explain here. But having co-routines means having a proper reactive framework that you use to react to events and update stores, which is where you keep your state. counter.data is a so-called StateFlow; the render function maps updates in that flow to the dom. In the example I have both a button and a co-routine updating the store via a handler lambda function.
Using koin here, just means keeping glue code out of places where it doesn't belong. It's technically optional but makes a lot of sense in larger applications. Because components are extension functions on RenderContext, I use a global variable to get to the koin context. That allows me to inject my dependencies into components with a minimum of fuss. Where Fritz2 gets fun is with more complex state using data classes, lenses, validators, routers and a few other things. And they also take care of styled components and they even have a nice component framework that you can use. Not for everyone and there's a bit of overhead in terms of download size. But great if that less of a concern.
The way mobx-state-tree uses generators as a coroutine-like affordance is worthy of study.
I would not at all be surprised or troubled if, having studied it, you still dislike it, but there are definitely ideas in there that I consider to be at worst -interesting-.
I can't begin to express how sad that makes me. Your component should have its own scope that it destroys when it gets destroyed, otherwise your coroutine leaks to the outside world when your component is gone from view.
I'm proficient with React hooks and functional components, but I will never bullshit anyone by pretending that they make any kind of intuitive sense.
Classes may have been more "code" but were clear to understand. I don't dispute that functional components are probably more efficient though.
Anyways, I've never had this problem with React hooks. I've been building complex dynamic UIs for years, and maybe I've grown to get around these kinds of things, but it seems like Solid.js is solving a niche problem at the cost of making hooks even less understandable?
They literally don't. For some reason the developers think that storing state in hidden global variables (linked list indexed by hook call order) is a good idea. You know, they could have at least defined useReducer inside the class. Of course that would sort of break the pretentious ability to define hooks as global functions that access component state. After all, you would have to pass the current component as the first parameter, imagine the horror!
I've tried using bare React in the past (after using Clojurescript), because I wanted my project to be more approachable for outsiders. But I couldn't really handle the (to me, and the author) unnecessary complexity that's added.
I would even say the Reagent version is even simpler than the Solid.js version, because you're using Clojure's Atom API rather than creating read / write functions. For the adventurous hearted I'd definitely recommend giving it a try!
Like the author of this post, I appreciate Solid's API because component's only render (i.e. run) once by default and then you define which sections of the component should re-render on changes by using "signals" provided by the library (e.g. `createSignal()` and `createEffect()`). In react, the entire component re-renders on every change and you need to specify which code should _not_ re-run. This was necessary because of the way react was created, but strikes me as fundamentally flawed.
Having used Solidjs for some pet projects, I've come to strongly prefer Solidjs over React. It's an evolution of react, so I've found my existing skills/knowledge transfers. This being said, Solidjs is brand new and the ecosystem is minuscule compared to React. For this reason, I plan to continue using React for the foreseeable future. One of the biggest weaknesses of Solidjs is the lack of a "nextjs" like framework. It appears work is being done in the solid-start[1] repo, but it looks like it's still years away from being fleshed out. I want Solidjs to succeed, but I'm not interested in being an early adopter.
I think a challenge for new javascript libraries/frameworks is that, for many developers (including myself), we're evaluating the whole architecture. The question isn't, "Which library has the best API and is most performant for rendering components?" It's which choice gets me to "usable app" quickest and most pain-free? Included in the calculation is the ecosystem, the build tools, the documentation, the deployment strategy.
For all these reasons, while I do really love Solid's API, React + Nextjs + Vercel (or another React stack like Gatsby, etc) ultimately provides a smoother development experience for the time being. It isn't enough to build a better React, someone needs to provide an easy to use build and deployment process for it as well.
I ended up giving up on my Solidjs experiments because I spent too much time debugging the build process and porting React libraries. It's still not obvious to me how I could deploy a Solidjs app to, e.g., a Cloudflare Worker and provide a `/api` callable functions endpoint for the application. I have no doubt that I could figure all of it out, but I'm not interested in spending the significant amount of time necessary to do so. I love the fact that Nextjs just gives me all of this. All of this is to say that, while the core Solidjs library is really "solid" (pun intended), I still don't think Solidjs is ready for new projects (unless you really like doing things from scratch).
>Having used Solidjs for some pet projects, I've come to strongly prefer Solidjs over React. It's an evolution of react, so I've found my existing skills/knowledge transfers. This being said, Solidjs is brand new and the ecosystem is minuscule compared to React. For this reason, I plan to continue using React for the foreseeable future. One of the biggest weaknesses of Solidjs is the lack of a "nextjs" like framework. It appears work is being done in the solid-start[1] repo, but it looks like it's still years away from being fleshed out. I want Solidjs to succeed, but I'm not interested in being an early adopter.
The chicken-and-egg ecosystem problem for new frameworks is tough. I've been working on Svelte stuff lately which has a similar problem but less extreme--the ecosystem is still much worse than React's, unsurprisingly, but it's also much better than Solid's right now.
I think Solid's primary branding is around performance and Svelte's primary branding is around it being easy. For getting things off the ground, I think "easy" is a much more successful approach.
Solid has done some branding around performance but the promotion is shifting to be more balanced, as Solid really isn't about performance. Solid's biggest priority has been to give the best DX for building performant applications that stay maintainable at scale and after years of work on the same project. Solid might seem harder than Svelte or Vue to get started with (although this is arguable IMO) but due to it's simplicity I think that it's much easier to master and understand what actually is going on.
Compare this to Svelte which has the goal of creating the perfect high level abstraction so that you never need to understand how things work and was originally created for smaller one off projects with much smaller complexity and no maintenance burden.
This is where I’m at, too — it’s faster and simpler than React, and less magical than Svelte, but it’s just too immature. As another commenter pointed out, there’s basically one contributor: https://github.com/solidjs/solid/graphs/contributors
> Like the author of this post, I appreciate Solid's API because component's only render (i.e. run) once by default and then you define which sections of the component should re-render on changes by using "signals" provided by the library (e.g. `createSignal()` and `createEffect()`). In react, the entire component re-renders on every change and you need to specify which code should _not_ re-run. This was necessary because of the way react was created, but strikes me as fundamentally flawed.
I don't particularly like React, but this strikes me as the one thing it got right; the only "always correct" thing to do is to rebuild the VDOM on any change, so that's the default.
Then you can be more selective about which parts as performance dictates.
That would be true if all a react component was dom output. But since it has side effects (rest calls, mutating state, effects), rerunning everything is the wrong thing to do in almost all instances.
Solid is nice and _seems_ to fix the issues with hooks, but as another comment mentioned, the challenge is with building at scale. It's unclear to me how this scales and where the sharp edges are.
React IMO trades off performance in exchange for ergonomics. These ergonomics bear fruit early on, but you start dealing with this debt very quickly. As "nice" as Redux is, I shouldn't need it so early. React is designed in a way that results in pretty terrible performance. I once wrote a React app, and discovered perf issues 2 weeks in. Any UI framework I used in the past would have scaled beyond this point without having to have the data layer rewritten. Frameworks of the past also had what seemed like way less "magic".
I totally accept that it's seriously nice to write, but how many trees has React alone burnt?
> If a linter is required to tell me when I'm writing a bug that is not immediately obvious, that is a failing in the framework
yep. They also commandeered the entire use* namespace just so they could get lint to warn when you put a hook outside the start of a function. Hacks on top of hacks.
Edit: after writing this I went and read the article. Same scenario I was bitching about lol
[0]: https://reactjs.org/docs/hooks-faq.html
Took me at half a week at least and I felt like a god after. Then I realized this was all phenomenal waste of time and I decided my next job was going to be cloud and backend focused. Only in the js world can you realize immediately as soon as your done that it's effectively worthless programming hassle.
The right way to do that exists in Elm. But I do not think there is a way to translate that into JS without requiring either a lot of boilerplate or very messy code
I've been working on a quite complex React codebase for 5 years now - it's not the biggest but still not so small. I was there on day 1. The team that works on it now full time is ~15 devs + 10-15 doing some minor stuff from time to time.
We've started using hooks about 1 year after they introduced them. Before we were very deep into redux and redux-saga. Now most of our codebase uses only hooks for most of the things we did with redux before - there are still some redux but most major parts are migrated to using hooks and contexts. It is probably the best decision we made after switching to TS. Most of our codebase is still very easy to maintain. Last year I went on a paternity leave for 6 months and I was pleasantly surprised how easy it was to adapt back.
Edit: forgot to write about Elm. I like elm a lot -even though I don't agree with how it's managed-. I even organized a local elm meetup :) But I think while it is very good in theory, it's not too easy to scale especially if you plan on hiring more devs. Also the way effects and data is completely separated from the view is not very ideal for bigger projects. You either end up having to touch too many files for a single feature or have incredibly big files.
That said, I still preferred React.createClass so I guess I'm somewhat off the beaten path when it comes to React.
My take is that if a feature requires a linter to be properly implemented then it's obviously giving the developer too much rope to hang themselves with.
Also it appears that they were introduced mainly for performance, but for some reason sold as the new "better" way of writing applications.
I actively avoid React and its ecosystem, because I believe there's just too much FOMO and "fashion"(for lack of a better word) in what drives its development and not enough meritocracy.
Those linter warnings with React hooks show just how much of a disaster it is. All of the "simplicity" that comes with hooks is just complexity that is now completely out of sight, and only appears once the code is run in a linter. That is even worse than having the complexity in the code.
You can write really performant software in React, but ergonomic (beyond stockholm syndrome) I would not call it.
It's also difficult to implement quality software engineering principles in a React application such that an application is maintainable, glacable and easy for someone new to a project to pick up.
The flexibility in its toolchain is nice, however
I still like using them however. I had a few issues when trying to do more abstract code.
They are highly, deliberately, emphatically procedural.
The order they are called matters! The time of when they are called matters! The number of times they are called matters! You get a different result from calling the same hook with the same arguments at different times! You get different results if the call site for them is in different places!
They do enable you to do some things in JavaScript that are more easily enabled with higher ordered function stuff in functional languages, but there is nothing ‘functional’ about how hooks work or how they are used.
And that’s okay! ‘Functional’ doesn’t automatically mean ‘better’ and ‘procedural’ doesn’t mean ‘worse’. For what they do, hooks (considered as the entire hooks ecosystem including linting extensions that help enforce the rules as if they were syntax errors) are a very clever extension within JavaScript syntax that let you pull off some very neat separation of concerns in a reactive code model.
‘Functionalness’ has very little to do with whether hooks are good or bad.
I do not want this handled at the framework level though. There are plenty of times in my day-to-day where that linter is wrong. If I were to add one of the dependencies (it's sure I should add) my component would re-render over and over. Sure, one could argue, "well, then you've poorly architected your code"... but THAT is where I feel like the failure occurs. Having to do write my code to please a paradigm. What that means is I only "sorta agree" with the design trade-offs. And much like Ruby on Rails, you either go all in, or spend your days hating the framework.
I do worry that Solid also has these "If you know, you know" edges though. So I'm trading looking at a function and assuming it'll run over and over with, looking at a function and thinking, "how can I make this thing run again when it needs to?" I'm not saying it's wrong, it's just another piece of tacit knowledge one has to learn when adopting a new framework.
On the JS/TS planet there's https://cycle.js.org , which comes close.
Looks even better than Solid IMHO.
Isn't the point of static analysis (including linters) that they catch those kind of bugs? Having that little red underline (or console warning) saves a lot of pain.
Hooks have completely changed the way I write React code. It's so much easier to test and reason about. Really the only problem is that the guardrails aren't more heavily enforced.
Hooks are a syntactic element of react code. Calls to them aren't legal inside conditionals or loops.
Full linting support smooths over that rough edge just as effectively as babel handling ?. syntax smooths over its absence in certain JS runtimes.
Deleted Comment
Can you branch on state or use loops over data in Solid.js? The reason _why_ React has a virtual DOM is to enable more interesting relationships between your data and your presentation. Anyone can make a framework that makes the source code for an incrementing number look pretty!
As an example of this point, check out the "Simple Todos" example for Solid.js[1].
In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.
I've been writing React and React-alike code for a long time. I think that fine-grained updates avoiding reconciliation are a good idea, especially for performance. At one point, I built a React-like library for Roblox and Lua whose most novel feature ended up being "Bindings"[2], which look sorta like Solid.js state containers. They create little hot-path data dependencies, but the bulk of your components still use normal React-like rendering.
I find this a totally bizarre complaint. I've spent the past few months working on Svelte stuff and I've seen people on HN make this same complaint about Svelte's templating language with {#if} and {#each}. Who cares? What is so wrong, exactly, with "reinventing a concept that's already in the language"? It does not make code any harder to understand or to write, and it does not harm performance (in this case, quite the opposite).
I would much rather have a reactivity model where I plug in completely standard concepts and patterns (a for loop) than one where I have to deal with a bunch of framework-specific, complicated ones (hooks). That Solid's reactivity primitives are familiar is an advantage, not a disadvantage.
So you end up with a secondary full featured language usually with worse IDE support, worse error messages, more surprising issues, etc. You need to understand the scoping mecanisms and if things go wrong hope there is a debug tool available.
And in the end those templating languages do not prevent you from mixing UI responsibilities from the rest of your code.
If you want a reactive model you can have one. I personally prefer explicit messages like calling setState.
Having templating DSLs in other frameworks isn't a deal breaker, but it's a pro of React that I appreciate.
Well, what can you tell me about TypeScript type inference for this custom DSL?
Nothing, inherently. Just like there's nothing inherently wrong with having extremely clear and simple rules for how to use hooks, and lint rules to identify when you're not following those rules. Nothing inherently wrong with either, some people just have strong distaste for one or the other.
Once you deal with larger amounts of data and need virtualised rather than fully-materialised lists, you start using different things in React as well. The fact of the matter is that if you care about performance at all, the simple ways are just insufficient, and the native language constructs were designed for procedural programming, not reactive interface rendering, which requires fundamentally incompatible semantics. It’s not even fair to claim that React uses regular JavaScript idioms—VDOM, hooks, the entire shebang is all about eschewing regular JavaScript idioms because they don’t scale. (OK, so there’s also the matter of transient state like scroll positions, element focus, and form field values; it’s not fair to say that React does all these things purely for performance’s sake, as the naive immediate mode approach would also break such functionality.)
I want to push back somewhat on this practice. Our computers are fast enough now, and the browser implementations optimized enough, that they should be able to handle thousands of materialized list items without breaking a sweat. Sometimes you really need virtualization, e.g. if the underlying data source has millions of records. But if the data can be fully materialized, then the implementation is simpler, and the user can take advantage of things like find in page. Virtualization is a convenient way to avoid the inefficiency of unoptimized VDOM-based rendering (e.g. with React, and yes, I know there are other optimizations available in React), but fine-grained updating (as in Solid) is even better.
I'm right there with you, but when React invents a whole markup language inside of JavaScript, it's not in much of a standing to make purity criticisms.
It's about as pure as you can get while having any sort of html-ish 'templating' whatsoever.
So, I'd say it's exactly in the right place to be making purity criticisms. They've taken the only approach that preserves the integrity of the code and doesn't involve build time magic.
It has no intrinsic semantics, and maps pretty much directly to actual javascript (which you can write directly or use an alternative helper for — hyperscript being a common one).
This argument is silly because it inevitably becomes a pissing contest of who can be most like vanilla JavaScript. In that case, why use JSX? Just write hyperscript calls instead. Why use React Router/React Context helpers? Just wrap your components using vanilla function providers instead. Why use React Hooks, which inevitably look like magic to a JavaScript veteran because the library inherently hides away some global state? I hope you can see what I'm getting at here.
I had/have your bias, but from playing with it I found a couple things:
1) Like React, you can swap out the template feature for a function call (or subcomponent). e.g. instead of
you can use functions and loops: 2) Even with my bias, I must admit I found the `<For...` syntax to be surprisingly easy to read and fast to eye-parse; much more so than other 'templating' (using your term) languages/macros/syntax I've used over the years.What I like about the React monoculture is that it's one less thing I have to care about. I can focus on the other aspects of my programs, beyond turning JSON into HTML.
I haven't used SolidJS so I'm not going to put it on blast. However, hearing people compare its reactivity model to Knockout JS gives me the heebie-jeebies, because Knockout projects were horrific to reason about (and test) beyond a certain scale.
Isn't this optional? Can't Solid use regular JSX loops?
For reactive control flow to be performant, we have to control how elements are created. For example, with lists, a simple map is inefficient as it always maps the entire array.
This means helper functions.
Actually, that is only half the reason. You can do whatever you want in my library (github.com/thebinarysearchtree/artwork) and it doesn't have any kind of virtual DOM or whatever Lit does, because you just create elements with JavaScript. The second reason, that everyone just assumes is the default, is that React has to use HTML-like templates and not just JavaScript.
That said, I think that easily the most difficult aspects of react revolve around how re-renders are triggered. Maintaining referential equality to stop unnecessary renders gets tricky when you are passing functions or objects. Suddenly you need to be using `useMemo` and `useCallback` and passing dependency lists that all have to be primitive values unless you want to memoize them as well. It can become such a headache that the official line around it mostly seems to be "make the render fast, don't worry about unnecessary re-renders" – good advice, until you hit a use-case where you need to worry.
Solid takes these problems and just vanishes them. UI state knows what its dependencies are automatically and only updates when they change – even in a sub-component level!
To be fair, I've never used Solid in anger, and moving to it would be a big ask when there is such a good ecosystem built up around react. That said it is easily one of the most exciting projects on my radar, and the developer Ryan Carniato seems extremely knowledgeable in the area.
https://english.stackexchange.com/questions/30939/is-used-in...
But then again even by british standards I am unusually sweary when writing code.
I haven't looked at Solid but I've used MobX extensively, and this sounds a whole lot like it. It integrates well with React, so you might give it a look if you've got an existing React codebase.
What React has going for it, is that it is predictable. That is an extremely important part of any tool.
Deleted Comment
> "Ohhh, an OO pattern with a couple of one-liner lifecycle methods is just WAY too much code! Higher likelihood for errors and worse developer experience."
...
> "So instead, I'm going to replace this with a functional pattern, that crams a couple of lifecycle functions into a closure, and is riddled with edge cases and common developer mistakes."
This article perfectly crystalizes why my career has tracked toward the backend over the past decade. All of the virtual ink in this article, and honestly most of the complexity in the field overall... and it seems to really all just boil down to, "I think this looks cooler."
Everyone always likes to blame the front-end engineers for being slovenly, but my experience is that; UI is just messy. It's just hard to program cleanly.
Not to mention, modern UIs are generally more complex than the old VB days (at least, my programs are): they have to adjust to various screen sizes. They are almost always client/server apps and need to account for more failure modes. They are built on top of a mess of a document layer that was never intended to be an application layer.
This last point is the reason the front-end space has so much churn. It's still an unsolved problem, and may never be well-solved, but I'm glad folks are trying to solve it. React was a step-change for me in terms of building better UIs. I'm looking forward to the next step-change.
Lastly, before someone makes the argument: no; we aren't going to stop building applications on top of the DOM + CSS. Not until someone comes up with an alternative that gives us the same ease of distribution and broad base adoption.
Edit: I should note, I'm a full-stack dev, and have been more back-end than front-end for most of the early part of my career. The backend is always easier for me at least, but I don't think it's because front-end devs are hipsters.
I'm an almost entirely frontend dev (iOS + macOS now, web development in the past) and this is what I've come to believe. It's easy to program UI cleanly if it's extremely basic or not very usable — but the second you want an animated transition between states, or to remember what the user checked on the last page so that you can keep it checked on this page, or any number of other things that you need to do if you actually want people to _enjoy using your software_, it gets much harder.
My take is it's because building UI is building software for humans, and humans often want behavior that doesn't allow for clean abstraction. Backend dev is more about building for other software -- not that it's easier, just a different set of problems.
In a way I find this meme even funnier now because of how backwards it is. Often times it's the backend that's really clean and organized and the frontend is a hot mess.
Deleted Comment
I think if you actually used hooks, you'll notice this immediately. It's not a matter of "looking cooler", a lot of language theory went into making UI development functional and less stateful, which in turn makes it far easier to manage complex applications. A small team can maintain a pretty huge beast if using hooks effectively.
IMO there's significant complexity in building a feature-rich frontend client. The "thicker" the client, the worse it gets. There's definitely a lot of 'I think this looks cooler' going around, but also we shouldn't forget that the need to come up with something better is partially a response to very real, very-not-imagined, frontend complexity.
Doing complex things is always complex, but when SIMPLE things are complex, then something is going very wrong.
And this is exactly where we are with most JS frameworks these days. Layer upon Layer of abstraction, and instead of the complexity the dev has to deal with decreasing as a result of it being abstracted into frameworks offering simple interfaces, complexity increases.
You're using this straw man to demean the author, but I think you miss an interesting point: it can be very hard in software developer to articulate the good and bad. "Uncle Bob" refers to this as "code smells", where you can't quite say immediately what's wrong here, just that you don't like it much. Something smells bad.
And maybe the point of Solid is to point out a mistake React made in their implementation of hooks. Hooks were badly needed, no doubt, but often times an okay solution to a really serious problem can look like a great solution.
The back end went through a lot of this already. That's why the patterns are more stable there, and why you feel less churn. The front end is exciting precisely because there's so much to still figure out!
To people who know what they are doing more code than necessary to accomplish a task is the code smell.
To people who are super insecure vanity code is required because patterns are memorized, so any deviation from the unnecessary boilerplate is the code smell.
UI developers focus on user interface, and much of that has to do with looks, style, and personal ergonomics. In a way, it makes sense that the engineers steeped in this type of problem set would be oriented to thinking this way. Even if they aren't initially, the probably will be over time.
In my experience, backend development is often simpler because you can move any state out of your own code into dedicated external components, like databases and queues. With frontend code, you have to manage state; there’s nowhere else for it to go.
Dead Comment
react isn't about 'hooks', 'jsx', 'top-down-state', or 'component-driven architecture'. All these frameworks are component-based, can have top down state only (or do bottom up in react), can use things like jsx/hooks because it's just syntactic sugar (vue has jsx support).
react is fundamentally about 'inputs changed, render this'. This got rid of a lot of issues with poor code because frameworks had crappy DX (angluar 1 scope nonsense, and overengineered DI concepts), and people were bad at tracking side effects because a lot of people just wanted a search bar with some cool features and not everyone was building sophisticated products.
That setInterval example is fundamentally against what react is, and is basically svelte/vue/(react + mobx).
I mean, pretty much all frameworks these days have that fundamental declarative model, react wasn't particularly innovative on that front (e.g. the declarative model already existed in angular, knockout, etc)
What the setInterval example highlights is that newer subsystems in React like useEffect and Suspense are bolted on top of earlier iterations that weren't originally designed to support these kinds of semantics, and the dissonance between API design iterations has become noticeable. This is a pain point that is relatively unique to React.
The growing popularity of Svelte and Solid are largely because their API designs align naturally with how people expect features to work, without people falling into pits of failure like stale closures and incorrectly wired dependencies. React is popular and it puts bread on your table and all, but pretending it doesn't have warts doesn't do anybody any favors.
React was absolutely a breath of fresh air when it was released.
Knockout was similar to Solid.js in that they both have functions that you call which then log a data dependency, then when the data changes the UI updates. This led to lots of pain, because instead of a plain value, you have functions which return values, and you need to be very careful about when those functions are called, otherwise the data dependency might not be tracked properly.
Angular had a similar issue, as its state-based observation relied on special scopes. Updates in the wrong scope could be lost or delayed.
React’s approach of only diffing the rendered UI rather than trying to drive updates based on diffs of the input data was vastly simpler, it was much easier to understand the data flow through explicit state and props.
Vue/svelte/solid do not fight against js, hence they do not end up in similar situation
React is unique in that everything in the component is within the render path, while the rest of the frameworks (that you've mentioned) doesn't.
you might be mistaking "side effects during render is bad" for "side effects is bad", the two statements are not the same.
Honestly, the boon of React is just how easy it is to create components, or at least how simple things were back in the day - it is exceedingly composable, moreso than AngularJS, Angular or Vue have been, at least in my experience. In React, your component can fit within a single file, containing simple syntax, especially for when you're making a pure functional component with no side effects or hooks. And even when you need to add something more complicated, you just have a method or two to change, essentially "progressive enhancement" for your code.
Though admittedly state management, or at least our current approaches to it ruin everything with endless boilerplate (Redux, Vuex etc.) to address an issue that may or may not be easier to represent, though some libraries certainly try (MobX comes to mind).
Of course, my experience leads me to agree with the article, in how React in combination of hooks sometimes is problematic, although in my case that was primarily because of render loops and how the stack traces are akin to JDK 8 NullPointerExceptions, where you couldn't see exactly what's causing you problems: https://blog.kronis.dev/everything%20is%20broken/modern-reac...
I'm probably wrong in liking class based components since those have other issues and Vue/Angular both feel a bit less productive in comparison, even if sometimes easier to reason about, with different tradeoffs to them. Maybe i should check out Svelte some day, but i guess it's all just one long slog of finding what works for you and what doesn't, much like it is with back end programming languages or even relational DBMSes.
It's really bizarre to me how poorly useContext works, in contrast to how good everything else in React is for the most part. Having a good, "official" global state management solution that requires little boilerplate would be a huge benefit.
Furthermore, a lambda local to a function can be a full-blown component (visible in DevTools etc.).
So if you need a component that is used in only one other component, you can neatly encapsulate it and make it invisible from the outside, which can be useful on occasion.
The actual article acknowledges that when it's read: "React isn’t truly reactive."
And the author also claims to love React and to think it made things better.
You're arguing with phantoms.
The 'inputs changed, render this' paradigm (and by this I mean reactivity, not the declarative model) has been around for much longer and is exactly what this post is about - React doesn't really do that, since it relies on you to explicitly tell it, via dependency arrays or setState calls, when to re-render. It is not fundamentally different from `.on('change', this.render)` code we were writing back in 2010, just a lot of syntax sugar on top.
That React managed to sell itself so well, while not actually delivering on the reactivity or performance promises, is the surprising part. I'm excited for the future as we finally move on from this era.
"React as Schelling Point" ?
If you have some recommendations or want to share your experience with working only with web standards I want to read them :)
Just look here at how much overall code is needed to do it right: https://github.com/ing-bank/lion/blob/master/packages/input/...
After you get through all the inheritance and mixins it's thousands of lines
The transition to functional components was to reduce the coupling between abstract functionality and DOM-related lifecycle events.
React hooks are mostly about expressing where and when you want to memoize a value, with the default being not to.
Once you learn what to look out for, and properly designing and review codebases at scale, these trivial issues don't happen all that often. Additionally, being able to specify memoization parameters explicitly brings extra flexibility and some additional design patterns.
https://reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-...
Newer frameworks are absolutely better for anybody who isn't sufficiently batshit enough to do that, but much though I enjoy react + mobx (especially react + mobx-state-tree) I've never got to the "I have the core source code in my head and can mentally dry run it as a desk check type operation when debugging" stage with them like I did with early angular so - with the level of jank inherent in its scope nonsense entirely acknowledged - I still occasionally miss it even so.
(this is mostly me being nostalgic, I think, the newer stuff is absolutely better but that was a fun few evenings and for its era damn but I could make that thing sing)
I'm having a lot of fun lately using Kotlin-js for example. We use the Fritz2 framework, koin for dependency injection (popular on Android as well for good reasons), and fritz2 relies on kotlin's co-routines and StateFlow for state management. It makes for a surprisingly concise code base. For example, the counter example from the article with that would look something like this:
There's a lot going on here that I can't explain here. But having co-routines means having a proper reactive framework that you use to react to events and update stores, which is where you keep your state. counter.data is a so-called StateFlow; the render function maps updates in that flow to the dom. In the example I have both a button and a co-routine updating the store via a handler lambda function.Using koin here, just means keeping glue code out of places where it doesn't belong. It's technically optional but makes a lot of sense in larger applications. Because components are extension functions on RenderContext, I use a global variable to get to the koin context. That allows me to inject my dependencies into components with a minimum of fuss. Where Fritz2 gets fun is with more complex state using data classes, lenses, validators, routers and a few other things. And they also take care of styled components and they even have a nice component framework that you can use. Not for everyone and there's a bit of overhead in terms of download size. But great if that less of a concern.
I would not at all be surprised or troubled if, having studied it, you still dislike it, but there are definitely ideas in there that I consider to be at worst -interesting-.
I can't begin to express how sad that makes me. Your component should have its own scope that it destroys when it gets destroyed, otherwise your coroutine leaks to the outside world when your component is gone from view.
Deleted Comment
Deleted Comment
Classes may have been more "code" but were clear to understand. I don't dispute that functional components are probably more efficient though.
Anyways, I've never had this problem with React hooks. I've been building complex dynamic UIs for years, and maybe I've grown to get around these kinds of things, but it seems like Solid.js is solving a niche problem at the cost of making hooks even less understandable?
I've tried using bare React in the past (after using Clojurescript), because I wanted my project to be more approachable for outsiders. But I couldn't really handle the (to me, and the author) unnecessary complexity that's added.
I would even say the Reagent version is even simpler than the Solid.js version, because you're using Clojure's Atom API rather than creating read / write functions. For the adventurous hearted I'd definitely recommend giving it a try!
Edit: Someone posted a Reagent counter example on codepen a few days ago: https://codepen.io/Prestance/pen/PoOdZQw
Having used Solidjs for some pet projects, I've come to strongly prefer Solidjs over React. It's an evolution of react, so I've found my existing skills/knowledge transfers. This being said, Solidjs is brand new and the ecosystem is minuscule compared to React. For this reason, I plan to continue using React for the foreseeable future. One of the biggest weaknesses of Solidjs is the lack of a "nextjs" like framework. It appears work is being done in the solid-start[1] repo, but it looks like it's still years away from being fleshed out. I want Solidjs to succeed, but I'm not interested in being an early adopter.
For all these reasons, while I do really love Solid's API, React + Nextjs + Vercel (or another React stack like Gatsby, etc) ultimately provides a smoother development experience for the time being. It isn't enough to build a better React, someone needs to provide an easy to use build and deployment process for it as well.
I ended up giving up on my Solidjs experiments because I spent too much time debugging the build process and porting React libraries. It's still not obvious to me how I could deploy a Solidjs app to, e.g., a Cloudflare Worker and provide a `/api` callable functions endpoint for the application. I have no doubt that I could figure all of it out, but I'm not interested in spending the significant amount of time necessary to do so. I love the fact that Nextjs just gives me all of this. All of this is to say that, while the core Solidjs library is really "solid" (pun intended), I still don't think Solidjs is ready for new projects (unless you really like doing things from scratch).
The chicken-and-egg ecosystem problem for new frameworks is tough. I've been working on Svelte stuff lately which has a similar problem but less extreme--the ecosystem is still much worse than React's, unsurprisingly, but it's also much better than Solid's right now.
I think Solid's primary branding is around performance and Svelte's primary branding is around it being easy. For getting things off the ground, I think "easy" is a much more successful approach.
Compare this to Svelte which has the goal of creating the perfect high level abstraction so that you never need to understand how things work and was originally created for smaller one off projects with much smaller complexity and no maintenance burden.
I don't particularly like React, but this strikes me as the one thing it got right; the only "always correct" thing to do is to rebuild the VDOM on any change, so that's the default.
Then you can be more selective about which parts as performance dictates.