![Cover image for The React Hooks Announcement In Retrospect: 2 Years Later](https://media.dev.to/cdn-cgi/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmve37at43zl83h1nbu4r.png)
This post represents my perspective of events and could depict an imagined narrative. I'm reflecting as I believe the Hooks announcement to be the...
For further actions, you may consider blocking this person and/or reporting abuse
It is nice that you took some time to take a step back and think about (some of) the changes the front-end world has gone through. It seems like we still haven't solved the question of what is the best way to write web applications. Which is a normal thing because best will have many definitions according to who asks the question. I see the same battles going on in the Haskell community as to what is the best way to structure Haskell applications.
Hooks are definitely extremely useful to Facebook, and the advantages in terms of packaging reusable functionalities are clear by now. The initial communication was more around the fact that classes are bad (?) or writing less code (?) and the likes. But one key value is reusability. The other is composability. These are desirable stuff. They help write programs faster (no better program than the one you did not have to write) and maybe in a more reliable way (reusing pieces that have already been tested).
We already know some of the downsides (you mentioned a few): stale closures, hook rules, etc. One that is not often discussed is that hooks introduce a much stronger coupling with the React runtime than ever before. That is what is behind the name, you are hooking in React internals, so you need to understand those internals. Hooks are far from being a functional abstraction as you need to know the internal details of their implementation (that becomes even more apparent when you try to test one hook in isolation), While people somehow think that React is following functional programming paradigm, it is the exact opposite that is true. It is getting away from the functional paradigm and going deeper into framework land. Functional components, as they are called, are rarely pure functions. Functional programming is not programming with functions. Then, all effects have to be controlled by the React framework. And again that framework gives you some surprises (cf. dev.to/poeticgeek/in-react-compone...) which I expect to increase with concurrent mode.
None of this really matters. People will always be able to write web applications with React if they put in enough effort, and there will be a whole industry teaching them those efforts. The same is true with Vue, Angular, Ember, vanilla-JS + web components, etc. I don't know how the 'competition' will involve but I think it is more of a promotional story and capturing developer mindshare rather than actual differences for the end user.
The issue that we have in front-end programming is precisely that because we use frameworks that are incompatible between each other, we end up in a situation where we duplicate a lot of work. You have a
subdivide
library for React (cf. github.com/philholden), then you duplicate that for Svelte, and for every other framework. We would write that in vanilla JS (or a web component) and everybody would be able to enjoy it rather than rewrite it.This sounds like obvious but this creates incentives for coalescing around a single framework to avoid duplicating work, or simply maximizing the reuse potential. That is a winner takes it all situation that favor the creation of monopoles, with its set of advantages and drawbacks. That is more what worries me going forward. Next.js is amazing but it only works with React. There are some reasons to this, but not so many. You could have a Next.js that is framework agnostic, there is just is no incentive to it. The same is true for a few other things (running applications on the edge, and more).
I do remember the moment when Facebook decided to change React licensing (for very valid reasons if you ask me). That served as a reminder that you do not want to have a dependency that is too strong over something that you do not control. As I said before, the React tie-in is only getting stronger.
This is fine. React is fine. So is Vue. But I hope we can achieve a vision of interoperability based on standards, that reflect the interest of several actors and the community rather than primarily that of Facebook. There is a lot to gain if we can achieve true reusability. Web components is a step in that direction. I wish that the corresponding standards would grow in use cases that they support and in adoption at least in the enterprise. Then folks can pick whichever framework they want, freely. Or they could choose none. It is interesting to have a look of how web components have improved in the last two years too.
I hope we will be able to have layout, server-side or edge-side rendering libraries that are framework agnostic. I see how WebAssembly is changing the game in the cloud and at the edge by being language-agnostic. There is a lot to gain there.
As a side-note, folks interested in actual functional programming applied to client-side web applications can have a look to that series of articles (as the author of Solid, the last one may be of interest to you):
In short: no context, no hooks, no weird stuff, mostly functions, mostly pure functions.
I think one lesson from OO history is that reusability while attractive is an elusive goal. Seems component replaceability is more important for systems longevity - so composability is where it's at.
Initially React's concept of a component was very much object-based where the render function's "associated functions" (handlers) were attached to the same object as the render function (function components at that time didn't need additional support or access to React managed state).
However HOCs were kind of an unwieldy way of composing non-presentational capabilities.
With hooks a component's "baggage" was moved from an object to a closure opening up compositional options more fine-grained than the components themselves.
Elm at its core relies on a VDOM as an adapter to the mutable DOM. I suspect that a Rust inspired imperative programming model of "reasonable mutability" may be more appropriate for browser-based applications.
It may even turn out that SPA's are an evolutionary dead end. They made sense back in 2013 when the web was still predominantly accessed through equipment with large core single thread performance connected via relative reliable networks. These days personal computing has shifted to handheld devices where low-power/spec small core CPUs are becoming more common and while on paper cellular networks can be quite capable, real world situations can quickly turn network quality to meh.
By using a ServiceWorker as a device local web server proxy MPAs can be quite responsive (Beyond SPAs).
Also I think that people need to manage their expectations regarding the impact that WebAssembly will have on browser-app development (not talking about cloud/edge here):
From that perspective I think that common expectations that WebAssembly will "normalize" browser app development towards what people are used to from desktop or native are misplaced. If anything a I think a leaner approach that maximizes the use of the browser's natively implemented capabilities (i.e. more than it's JavaScript engine) is indicated (e.g. Svelte seems more like an extension to the browser rather than React which seems intent on "replacing" it in it's own image).
My point about WebAssembly is that by targeting a common standard agreed on by many vendors, developers have the freedom to develop in several languages (Rust, C, C++, Go, and a few more in a growing list). That allows to reuse and compose existing software written in different languages. In short, we have reusability, we have interoperability, and we have composability (well the specifications still have to improve in that direction with interface types and module linking to make this friendly but we'll get there eventually). That is a benefit of standards that are agreed and negotiated on by several vendors. I am opposing that to the closed world of frameworks.
Before WebAssembly we had Google NaCL as an attempt to run native code written in other languages. That was proprietary and not really interoperable. WebAssembly is opening a world of software in the browser, in embedded devices, at the cloud, the edge, you name it. That is why I say that there is lot to win to achieve interoperability instead of duplicating libraries or solutions for each framework.
So that is an example of the benefits of standards. Standard can succeed when they are enough low-level and well-designed that you can construct higher abstractions on top of them.
Even Go is exhibiting the problems that I was highlighting. A simple "Hello World" file generates a 1.3 MB
wasm
file. One way to whittle that down is to use TinyGo - but this comes at the cost of features and acceptance of limitations.Many consider C#/Blazor another hopeful but that also clocks in at a 1.6 MB
wasm
for "Hello World" due to the overhead of the .NET CLR.Compare that to a suggested JS performance budget of 170KB (0.7MB uncompressed) for an entire page (where the runtime already exists in the browser).
And people who think that the Networks will save us will be disappointed. Consider this quote:
Networks are experiencing a similar phenomenon. Subscriber growth and demand is outstripping the gradual theoretical improvements in peak performance and capability, often resulting in a degradation of the average network quality experience in some areas.
The majority of existing software hasn't been written with the constraints of the web in mind - be it issues like the "subset of the Go" supported by TinyGo or that web content has to work without the luxury of prior installation or the constraints imposed on generalized distributed computing by the "laws of physics" or security.
As a result there'll be a lot of rewriting where people are expecting reuse. WASI will improve things but at this point it is unclear how far browsers can actually go, beyond existing Web APIs, in exposing "operating-system-like features" to remote content.
Sure. But the reality is that the browser is the client runtime for the web, not the operating system the browser is running on (and in the mobile space the OS fragmentation is getting worse, not better).
Yes but in embedded systems, the cloud, and the edge we are talking about installed software, not just-in-time delivery over a perhaps questionable network which web content has to contend with - installation of PWAs is optional (usually to support offline functionality), not mandatory.
The issue is that for decades we have enjoyed the benefits of Moore's law to create the additional headroom needed to afford new abstractions - but that's done now.
As developers we are entering a phase where we need to make harder choices of where we are going to spend our users' runtime resources. This has been historically an issue for the video games industry which typically had to work on low(er) cost platforms. So they have been using Data-Oriented Design (Shooting Yourself in The Foot With OOP) and Entity Component Systems - and other industries are starting to notice.
So it seems only natural if the web at some point will divert away from the monolithic (SPA-style) apps that are preferred on desktop and native.
Related:
Why the #wasmsummit Website isn't written in Wasm
Polyglot WebAssembly
Yeah I think I understate this. I do mention that by providing their own primitives they now have an opinion, and basically push some of the 3rd parties out. But you are saying it's more that by encouraging proprietary primitives it ensures lock in. All that being said I always felt React = FP thing was fictional, marketing hype thing. React = React simple as that.
I think your FP stuff is in the same category as XState where some people are actively searching ways to pull all state management out of React for this reason.
That being said I don't feel this is some sinister or even Facebook driven motive. Reactive libraries like Vue or Svelte have the same proprietary buy in on their primitives. Sure you can export the reactive system and use it elsewhere, but every detail in how it interacts with the templating and components is very much specific. These systems are just as invasive and promote lock in.
I think React was attempting to provide a composition model they could do smart things with. When you consider things like Suspense/Concurrent Mode and like these isomorphic stories. The only way we can do things smartly is to know what we are dealing with. When we are we done rendering etc.. Have the means to be able to orchestrate at a higher level that was part of their offering. You can do it other ways but they saw the need to ship with it for the next feature set. I think that was the motivation with the timing of this.
And that's the problem with the standards. Framework authors still feel like they are moving this stuff forward at such a pace standards aren't really catching up. Even if the client side render is becoming well understood, SSR and isomorphic is opening up completely new doors. Even Svelte in the client with their approach to animations is suggesting that standardization is a ways off.
So while I'm saying I think we will see this work to be agnostic. It will be the lowest common denominator of libraries. You will see the frameworks themselves accelerating in directions that aren't generically followed.
Personally I was on this train of standardization about 5 years ago. But as I worked more on my frameworks and realized the overhead cost, It is achievable but never quite as good. I wrote libraries to generalize reactive rendering (DOM Expressions is still bring your reactive system), swore by webcomponents, and thought that I'd land on TC-39 observable spec as basis for reactivity. In the past 5 years I realized everyone of those was a trap, or mostly useless beyond simple demos. It was something one can do but would never be the "best" way. Not today, never. Maybe new specs and new approaches changes that but fundamentally these do not align with the direction of things as they unfolded.
Of course there is enough of a desire for this that it will be a thing. And there will continue to be this tension. And it will be completely doable. However, there will be those (mostly from frameworks) that know their specialized solution is more optimal.
Generosity/portability/maintainability always comes with some overhead and performance loss. In traditional non-frontend development we always talk about breaking down into smaller, purer, more testable functions, using more interfaces that help doing better abstractoin, avoiding lock between business logic and particular frameworks. All these come with runtime overhead, or, at least, more compiling time for, say, rust.
It's always a tradeoff. ;(
Is Svelte not compatible with the Observable API? The library Effector is too. What precludes it from being the best way do you think?
I mean sure you can always make an adaptor. And it looks like Svelte supports Observables right out of the box since their stores are subscribables. Most other reactive libraries can take a subscribable and have it write to one of their own primitives in a similar way.
So to clarify I don't see any problem if you prefer these different global state managed solutions. And I'd welcome people bringing other reactive libraries for global state management if it made sense as the interopt layer is fairly painless.
I was just saying that using Observables for the frameworks internals is less optimal than lighter weight subscription mechanisms. It can be done and I went down that path at one point, but it was bulky when it came to my templating goals. Mostly that spec Observables are cold, an unicast in nature, and we often want our reactivity hot, and multicast at the framework level. You almost always want Behavior Subjects using the RxJS term and mapping between them constantly is a bit tedious. I've seen some work to reduce this overhead though. RxJS-Autorun is a great little library geared at bridging that gap. I already played around with making a version of my renderer to use it, and it wasn't bad.
You can even use composable primitives to bring them into the library closer and replace local state management. But in those cases using the libraries own primitives is going to be the most performant. You remove any translation overhead. In most cases this is going to be minimal, so I wouldn't sweat it if it's your preferred experience. But like Svelte or React team is only going to be optimizing for their localized cases. Even Svelte stores adds a little bit of extra on top of Svelte's component reactivity. Again nothing worth worrying about, but there are tradeoffs. Mostly that the frameworks will continue to build more features that might demand more interaction points that will make these things potentially bulkier. It's really a decision of how much you value the cleanliness of your logic and how much you want to leverage framework unique traits. I'm not going to say anyway is right for all cases.
Yeah I see where you're coming from, you're always going to have to build some sort of layer on top of observables and whether that meshes with your design goals depends. Thanks for getting back to me!
I still find hooks to be inherently unfriendly to the human mind. Classes are friendly and were the fruit of many years of programming evolution/wisdom, which hooks basically throws in the trash, all because of hatred of inheritance. Inheritance is only one feature of classes. They also serve as natural mental/organizational models. With hooks, figuring out what is going on based on reading code is much harder. If the goal is functional programming and code sharing, I've never understood why that could not be more simply accomplished within the class paradigm by using functions instead of methods and passing them what's necessary, even "this." func.apply(this, ...) isn't that hooks in a nutshell?
With respect to state management, it seems quite clear that we need both global and local state. It would be nice if both were handled in the same way so that they could be used interchangeably depending on scenario. I view that as the biggest problem. Mobx solves it quite nicely. And by tracking change automatically at all nodes of a state tree, offers optimization that would be hard to exceed in any other fashion.
I mean that is a bit of what of Vue 3 has with their Composition API. The reactivity is independent somewhat from the Component lifecycle.
You might like my library Solid. It's basically if you created a library purely from MobX (I needed to make some changes for performance reasons). No VDOM just reactivity. A simplistic view is everything is a series of nested
autorun
s. State management local and global is homogenous. It's all just a matter of scope.That being said I like the function component part of React. So when I make the comparison to MobX it's based on the reactive data and not all the decorator/decorating stuff that people associate.
This is the first I've heard of
JSX-Lite
. It's a very cool idea, but I don't think it really "compiled to Svelte" how I would have hoped...Thanks for taking a look and great catch - try this to see how to accomplish what you are going for
JSX Lite has some basic rules that must be followed that we are working on docs and an eslint plugin to catch and guide to the right practices we properly support
Yeah, this makes sense. To be fair, I didn't read the docs so it makes sense now that what I was trying didn't work. I may still try this out for one of my Svelte projects.
To be fair I believe it is still in alpha. I think there are a lot of rough pieces right now, but it is interesting they've conceptually have a means to pull off this sort of thing by identifying the similarities in state management. There are a number of edge cases I believe in terms of execution timing, so we will see as it develops, but the idea seems promising.
Yeah, for sure. It's a very cool idea. I really hope it pans out. I moved my personal projects off of React and onto Svelte, I really miss JSX vs their template syntax.
Where I stand these days is Svelte is great, but I don't want to give up the good parts from React.
My personal slant is what lead SolidJS to use explicit syntax with JSX while doing the compilation/reactive no-VDOM thing. Turns out it is really performant being explicit while harnessing the flexibility of JSX.
Great article! Love the questions raised and good to know I'm not the only one questioning where do we go from here
Class components where horrible, thank god we have Hooks now! It is amazing how clean my components are with hooks.