React Labs: What We've Been Working On – February 2024
react.dev
react.dev
The author uses an example of needing to initialize and dispose of a variable, and you must do this manually each time, for each variable. With a hook, you can abstract this behavior and say, given this lifecycle, I need this variable to be automatically initialized and disposed at the correct time. I write it once, and don't have to think about doing it manually anymore. Hooks are an abstraction over lifecycles, just as functions and loops are an abstraction over repeated procedures.
There's got to be another word for 'magic' to describe this kind of behavior because this is on a whole other level. People complain about observers, or transpiling being too magical, but at least those are straightforward in principle. I can see Rich's video on how svelte works and more or less get it, it's just that it happens implicitly. Semantic value here would depend entirely on the data type; I have no effective way of being sure of what is being implicitly generated.
As far as I understand that quote, it's just about "reference equality" vs "value equality", something most programmers deal with often but maybe frontend programmers aren't used to it? Not sure why the quote doesn't include that naming for it, maybe it would help people connect it to concepts they already understand.
FWIW, I also think React has gotten to complicated and bloated, but I've been thinking that since hooks were introduced if not before, and mainly use React indirectly via Reagent, not directly.
So I can totally explain someone how there is no pass-by-reference in Go, and I can also explain why equal sets of different identity are not really equal in JS (following the records proposal, with my fingers crossed, and happy with it because it's a language change), but when libraries introduce different semantics via their own compilers, that's a line I don't like getting crossed.
That's actually why I never really got into Svelte.
A mutable model that you can reliably understand is better than an immutable one that leaks.
The pain and suffer of hooks all roots from the mismatch between a dogmatic belief in the superiority of immutability and the harsh reality of the host language that is JavaScript.
React's render cycle is fundamentally mis-aligned with JavaScript since the re-render requires that you correctly manage state and references in the path of the render cycle by moving it out of the way with a hook.It all feels quite unnatural as someone who has been writing JavaScript since the last 90's.
The key is to write your own hooks specialized for the project, then the complexity is isolated into one place where you are expecting it, instead of dispersed across your project. If you are trying to write everything using the built-in hooks then you’re going to have a bad time.
> you’re going to have a bad time
Have a good time and use any of the other options available. Vue, Solid, Preact, Svelte, Lit, Vanilla.They go on to explain :
> Under the hood we use a custom code representation and transformation pipeline in order to do low-level semantic analysis. However, the primary public interface to the compiler will be via Babel and other build system plugins. For ease of testing we currently have a Babel plugin which is a very thin wrapper that calls the compiler to generate a new version of each function and swap it in.
> As we refactored the compiler over the last few months, we wanted to focus on refining the core compilation model to ensure we could handle complexities such as conditionals, loops, reassignment, and mutation. However, JavaScript has a lot of ways to express each of those features: if/else, ternaries, for, for-in, for-of, etc.
[0]https://react.dev/blog/2023/03/22/react-labs-what-we-have-be...
Also I don't think useMemo or useCallback were a reasonable compromise ever.
I find very little reason for anyone, except services in the insta scale, to actually use this abomination, since CSS and html deliver the same user experience with plain SSR.
React is still not a better option for them.
The truth is that React was very good and probably would have remained pretty good to great even today if the original form had been iterated upon.
What we’re seeing today is nothing like the original react and should have been a completely different library in the first place.
I suspect if this completely library had been created without the advantage of piggy backing off the original highly successful React library’s fame, it would have made little to no headway on its own strength.
Heck, I’d be open to an argument that what we’re seeing today, a DSL that’s focused on making NextJS work, could even be considered a 3rd or 4th different library under the React moniker, after original React, hooks based React (even here the goal and meaning of hooks changed midway) and then the current NextJS DSL.
Some people (me) use React to build interactive web apps. That’s what it was designed for, and it works rather well. SSR is completely useless for me.
This is not React these days at all. Maybe when it was just an actually useful library. Not a hope now.
> It became the most popular in the landscape where all kinds of other frameworks existed
It became popular because it was simple, when it was simple. Then they did what all JS developers do. Find a problem for a solution.
Hooks are very non obvious / full of gotchas, and made react code no longer "just javascript".
now it seems the magic Vue had has gotten better, and was copied over but worse to react.
if react can fix it by compiling it away, cool. but fixing computer bugs is even less approachable than webpack.
React is the new IBM: you should learn it, you should understand its faults, you should probably still deploy it. You’ll never get fired for picking it, but it’s going to be expensive, bloated, difficult to get right, and it’s going to be joyless implementing it every step of the way. React is “the status quo mindset — the biggest enemy of pure innovation”.
I think the project has completely gone off of the rails and is now going through these cycles of self-inflicted complexity only to be solved several releases later with additional complexity. All the while, not really addressing the core issues that affect DX and ergonomics.React is the new IBM.
I understand the problems in scaling UI development. Just don’t think the solution is to build another layer of complexity atop the shaking foundation.
Why would we try to solve UI development issues any different than other parts of the stack? Building layers on top of shaky foundations is a true and tested approach in computer science and seemingly the only way to move forward somewhat.
They keep solving important problems like memorization bugs, but these problems are all self inflicted. The API keeps getting more complex, and with a goal of backwards compatibility everything is additive.
Actions as a generic concept look interesting, but the idea of having every library expose both onSubmit and submitAction is just going to make things more confusing. I guess you use events for old code, and actions when you want the option to send the event to the server depending on the "use server" directive or pragma? And when the next thing comes along, I guess every library exposes 3 different ways to handle a form submit event?
But since maybe 1-2 years, I am back and betting on React for most of my serious projects (for the ecosystem, the ease of hiring, etc), but the most important point is the following:
React backwards compatibility is really good, and will stay so for a good reason: a LOT of Meta’s UI code is using old features (classes syntax etc), and Meta cannot afford to break those. If there are breaking changes, they must be “codemodable” (so, usable by everyone).
Meaning in terms of stability, I know my codebase today will still work fine in years ( or upgrade-able with minimal efforts). Of course there will be new shiny features, but I or my team will not have to rewrite old code all the time following tedious migration guides.
disclaimer: I am kind of biased as I work at Meta, but far from React.
The person I was replying to did minimize the effort involved in upgrades by enclosing it in parentheses.
The incredulity is based on experience. Sure you can follow the upgrade guide, and if you work at Meta there are many people that know about weird edge cases. If you don't work at Meta, then you may be spending time chasing down bugs in production on your own. That gets expensive.
Maybe Svelte is also like Vue, I haven't tried it. I've heard many react horror stories but I've never used it, so I don't know how true they are.
No compiler necessary.
If React created better extension points for pluggable reactivity we wouldn't even need observer() wrappers. This is all getting kinda bonkers.
Let me try to explain this differently:
A typical reactive framework architecture (RX, Knockout, etc) looks like this
Events -> Observables -> Computed values -> Subscriptions -> DOM updates
It aims to provide a targeted DOM update in response to event as soon as possible, but it has several major disadvantages:
1. Batching and prioritizing capabilities either don't exist, or are limited and generic, in particular by default they are not tied to UI structure, because the framework itself doesn't want to be aware of UI structure, it just wants the framework user to provide event sources and write update targets manually.
2. Computed values are computed if corresponding subscriptions are active. In contrast, React hides state propagation logic inside the component tree, so the computation happens only when components are actually rendered.
Issue 1 is not relevant for MobX-React case, because React is doing UI update batching and prioritisation. But issue 2 is very real, especially when using React Router. I had multiple cases where Computed values were computed at unexpected point in time and used observable values that were not initialised, while corresponding React components were not even visible on the screen.
Preact has been looking increasingly attractive as a library that does its own thing and nothing more. Anyone knows if preact signals are basically a drop in replacement for mobX?
I've seen plenty of spaghetti code with just about every other React state management (reactivity) approach. The benefit of MobX to me is I can write a more traditional "domain" layer and wire the view in on top of it keeping a good separation between business/domain logic and UI concerns.
Why anyone would start a project with react in 2024 be beyond me.
Even knowing why they are needed requires a deep understanding of what is happening.
Definitely feels like complex implementation details leaking upwards and influencing the public API.
I’ve worked on many projects that implemented object-oriented programming in C, and the impedance mismatch there was much more tolerable. That should be a warning signal that something is amiss with hooks in JS.
They made an embedded language extension for JSX. Why not a similar separate language for hooks, to clearly delineate when you’re working with the React state machine rather than ordinary JS state?
I'm not sure if it was the react team adjusting the message and pushing for hooks in user code, or users throwing hooks at every problem when that wasn't recommended, but it definitely got odd track.
I still like hooks for internal code in libraries and frameworks, but they ruin client code IMO. Unfortunately hooks are here to stay and there's really no way to avoid them in most apps today.
I’m open to having my mind changed, but I really don’t like having abstractions cross the network boundary.
This describes it very well. I recently started writing React code after using Vue 3's composition API for a long time, and it's hard to explain just how much more low-level and manual the primitives feel.
I wish Remix was preact. It looks like it has parts of preact and svelte I really enjoy.
I've been using Next.js since 2016/17 at small and large SaaS scale) -- I feel like they're building it like a VS funded SaaS now. The added complexity doesn't really pay off.
I really feel bad for anyone who decided to go with that stack.
I'm not sure why you're jumping to discussing Redux here - we're a completely separate project from React, and nothing about the parent comment mentioned Redux.
That said, we specifically created and designed RTK to _eliminate_ boilerplate, so I'm not sure what "boilerplate" you're referring to here. Could you give some specific examples? What concerns do you have?
That said, we specifically created Redux Toolkit to drastically simplify standard Redux usage patterns. Sadly, despite it being the default way to write Redux apps for several years now, there's still a lot of legacy Redux code that isn't following our recommended patterns.
See our "Migrating to Modern Redux" guide for details:
- https://redux.js.org/usage/migrating-to-modern-redux
as well as the "Style Guide" best practices page:
It's a different domain with a different skillset.
With regards to incentives, these newer react features only meet their goals if the goal is to use react the open source project as a funnel for Vercel, the for profit hosting company.
The project I'm most excited about in recent years is htmx. Sadly I've yet to have the opportunity to work on any real projects with it. Some day, fingers crossed!
I've been using hooks since they were introduced, in several teams (at several different companies), and I've never experienced them being complicated to understand, either for myself, or for team mates - even juniors who are new to React. In my experience, it takes very little time (<1 hour) to understand the basics of React, and once you have that mental model in place, hooks fit in immediately.
I wonder if it's the case that many people on HN are just used to some completely different libraries and thus are coming in to React with a completely different mental model? And that's the cause of this sentiment being so common here.
Nope. For me React was the first frontend framework I learned. The mental model of Class components was really easy to understand. I have since "learned" hooks, but they are a constant source of mental exertion for me, and it's very easy to make mistakes. Kind of like all the other "improvements" that they brought to React since Class components.
[0] https://medium.com/@dan_abramov/making-sense-of-react-hooks-...
What you're probably thinking is "it's faster to write a TODO example app with hooks". That's not really relevant for actual software development.
It seems that framework creators are constantly making tradeoffs where they are making the easy things easier at the expense of making the hard things harder. That's the wrong tradeoff to make.
> What you're probably thinking is "it's faster to write a TODO example app with hooks". That's not really relevant for actual software development.
For me, that's not the case at all. I think with hooks, it's easier to reason about what values are actually used during what renders.
Hmmh. Can you provide me an example of what would've been "unclear" if the value was just state in a Class component?
There is a very strong implication in the docs and even the overall solution of hooks to ignore the details and just "go with it".
Just do it™ our way™ because reasons™
Generally not healthy for building the level of understanding it takes to debug complex react code.
Also I deeply on a spiritual level detest the way that it very strongly incentivizes pushing business logic to the view layer.
Here’s how useEffect is described in the old documentation:
https://legacy.reactjs.org/docs/hooks-effect.html
> The Effect Hook lets you perform side effects in function components
Here’s how the new docs explain useEffect:
> useEffect is a React Hook that lets you synchronize a component with an external system.
These are dramatically different claims about what useEffect is supposed to be for. Dig a little deeper and Reaft developers will now explicitly tell you to not use useEffects for side effects, which again is the opposite of what they explained it to be.
The same change in how they’re actually supposed to be used has been true across all the books to a lesser or greater degree.
Maybe React has finally stabilized and it’s easier for new devs because they’re learning the more stabilized version. However, I suspect it’s easy because they just happen to be within the same cycle of understanding in React. Much like how React was easy for devs coming to it nearly a decade ago, until the devs changed everything about it. New devs may just not be deep enough into the new cycle to experience the pain of all your understanding being wrong.
Now, to be completely clear, I have no problem with the changes. I think change is good and I have successfully worked with languages where the change has been even more dramatic than React.
The difference with React, I’ve increasingly come to realize, is that the developers will make radical changes in how they understand it to work, while still gaslighting you that nothing really has changed.
Hence the massive effort they made to convince everyone that hooks were just a different approach as classes and they both would be first class citizens forever, when they first introduced hooks, only to subtly and quietly change the narrative to hooks being the future of Reaft and recommending hooks.
They make these shifts all over the place without ever announcing it and convincing you to believe that what they’re saying today has always been the case.
More honesty towards how they’re changing React and what those changes mean would have gone a long way to reduce the absolute confusion floating around the react world.
No, it had nothing to do with making it "simpler for beginners." It was to functionalize state changes in a way that was impossible with classes and other OOP constructs like mixins. There is actually a great issue thread on the Flutter GitHub that explains exactly why other solutions do not work correctly when compared to hooks [0]. What people don't get about hooks is that they are an abstraction over state and app lifecycle changes. It is better to think of them as akin to closures but over lifecycles, not just holding state as closures do.
Interesting. I assume you are referring to this comment in particular -> https://github.com/flutter/flutter/issues/51752#issuecomment... ?
Open up the announcement of hooks by Dan Abramov and listen him.
What makes you think this was the goal? React team has been trying to get rid of 'this' for a long time; I believe I saw someone say that it had some undesirable consequences for the fiber architecture and for the "concurrent mode" that eventually was transformed into a set of concurrent features. Alternatively, it is possible that they wanted a better reactivity model than the one based on component's lifecycle. Why does this necessarily have to be simpler for beginners?
> ...we want closures to capture the values we rendered with, and to keep "seeing" those values forever. That's really important for concurrent mode where a notion of current value doesn't really exist. Hooks design models a component as being in many non-clashing states at the same time, instead of switching the "current" state (which is what classes model well). People don't really need to think about these details, but they're motivating the design a lot. [0]
> In Concurrent Mode, render may run more then one time, and since this in a class is mutable, renders that should be the same may not be. [1]
[0] - https://github.com/reactjs/rfcs/pull/68#issuecomment-4778866...
[1] - https://tkplaceholder.io/why-function-components-fit-react-b...
At least for now, I don't see any urgency to switch from React. There's more React developers than Vue, Angular, Svelte, htmx, and Solid combined. React is not quite as ergonomic as Vue or Svelte, but all are swiftly approaching feature parity.
Vue and Sevlte don't seem to represent a big enough boost in productivity to justify rewriting an entire generation of codebases.
Anecdote, we switched to React Native from a thing called Nativescript, primarily motivated by the dwindling development and support, and the huge overhead that Nativescript brought.
But I have to hold my hand up and say I didn't like Angular and the redux-style beating around the bush either.
Also, take a look at this response from 2021:
"Like I said earlier, it was always the intention that we're not going to be able to work on it full-time... [I]t was intentionally designed for this kind of sporadic development. We would get critical fixes out as soon as posible, but overall, starting with 2.0, it's mostly in maintenance mode and does not strive to be the best tool for production React apps. It is a tool to get started and get something running fast. Perhaps, it's not even best at that anymore."
https://github.com/facebook/create-react-app/discussions/117...
Make sense, but the lack of an "oficial" solution doesn't help https://react.dev/learn/start-a-new-react-project
Html and css have only been getting better over the past two decades; they are not the problem; no need to resort to inline styles and tables. As for going back to server-side templating with php, this is a very good option.
CRA was useful when it first appeared because the whole space was a big mess. But it very quickly outlived its usefulness, and its approach was really problematic in my opinion. The config was too complex to eject and modify compared to what you could do yourself with Webpack directly.
The tools got much better, it's much easier to just set up a basic Webpack or Vite setup today compared to when CRA was originally created. And the simple configs are much simpler to debug and understand when something doesn't work as expected.
Yes agree, but writing and marinating config for multiple projects just don`t scale. Not to mention we need something dirty and fast.
Redux was kinda of a poor man's elm but it got the right principles. However the JS community hates boilerplate code so, new, more complicated abstractions appeared. Also, it was too easy, with redux, to shoot yourself in the foot.
Since then, with hooks, things are just getting harder and more complicated in my opinion.
Context updates all components while Redux and similar state management tools, because they live outside of React, do not. That's the main reason to use true state management tools over context. If you have a relatively small application or you don't care about that, continue using context. The article is also by a Redux core maintainer so you might call that biased but they know what they're talking about, since they're privy to React design decisions much more than regular users.
All the components that consume it. Not the whole tree under the provider.
It is well documented and implemented like so.
https://react.dev/reference/react/useContext > useContext is a React Hook that lets you read and *subscribe* to context from your component.
Emphasise mine.
I once counted how many things you had to do to add new button with new action (it was when class components were still acceptable) - you had to change 4 files in 7 different places. This was completely insane for me. After little fighting with Redux (so I could pass nonserializable actions) I went down to 2 changes in 2 files (to dispatch action and to reduce it).
Redux is better now but I do not think its principles were that good.
- It was never necessary to write `mapDispatchToProps` as a function. `connect` always supported an "object shorthand", where you passed in an object full of action creators, and we recommended that as the default: https://react-redux.js.org/using-react-redux/connect-mapdisp... . That said, yes, with hooks we just give you `const dispatch = useDispatch()` and let you use that as necessary.
- The split across multiple files was never _necessary_. Splitting code like `actions/todos.js`, `reducers/todos.js` , and `constants/todos.js` was _common_ (and admittedly shown in the docs), but Redux itself never cared about how you organized your code at the runtime level. The single-file "ducks pattern" ( https://github.com/erikras/ducks-modular-redux ) was proposed very early on and was always something users could do.
- Labeling "global reducer, serializable state, serializable actions" as "not bringing anything worthwhile" seems like a complete misunderstanding of Redux's design and purpose. Redux was created to give you a consistent data flow architecture, and the ability to make it easier to understand when, why, and how your state gets updated. Centralizing state and having consolidated reducer logic means you _always_ know "go look at the reducers" to see what state the app has, what actions _can_ occur in the app, and how the state gets updated for each action that can occur. Serializable state and actions enable the Redux DevTools, which show you the history of dispatched actions, the action and state contents for each dispatch, and the final resulting state after each dispatch. So, the design constraints are fully intentional to give you the benefits that make the app's behavior easier to understand.
Finally, note that Redux usage patterns changed dramatically in 2019, with the release of our official Redux Toolkit package and the React-Redux hooks API. RTK includes methods that simplify all the common Redux use cases: setting up a Redux store with good defaults, writing reducers with simpler immutable update logic (and getting all the action creators generated for free), patterns like async requests / reactive side effects / normalizing items by ID, and even our RTK Query data fetching layer for fully declarative data fetching and caching.
Redux is not the right choice for all apps, and there's a lot of other good alternative tools in the ecosystem. But Redux's design _is_ very intentional, those constraints were put in place to enable the desired benefits, and Redux usage today is much easier thanks to RTK.