Understanding React Compiler
tonyalicea.dev
tonyalicea.dev
1. They introduced hooks which must be called in the same order for every execution. You can't put hook inside `if`. Hooks are based on magic. This is terrible design.
2. They didn't adopt async/await, inventing their own suspend stuff.
3. Now this: they just convert the source language into something else.
The JSX idea is gold: introduce XML-like template syntax into the language to avoid need for foreign template language. Make XML templates valid at startup and even with proper types for TypeScript. This is very important advancement compared to text templates which are used everywhere else.
The stateless render function is good. Emit desired-state, let framework to reconcile current state to desired state.
But rest of React is not gold.
which is completely dependent on a compiler since it bakes in all of the updating logic at that stage. I haven't done big projects with it but for little projects I have been amazed at the speed and the small size of the bundles.
The worst part of react is all the FUD hacker news creates about it every time they try to innovate to make the web's defacto framework better.
React does let you do a lot in async, when you are managing the rendering of components something like Suspense could be the right idea but I have a very big application that breaks some of the rules of React but gets away with it and I don’t know how it will do in React 18.
In a React-based project, I managed to use RxJS through react-rxjs[0] to manage events and state. Thanks to it, I was able to fully streamline state management in that project.
Async and await is not useful for React, Suspense is done out of necessity. It's have to be generators if anything - but Suspense is older than wide support of generators and async/await too.
You don't have to use hooks nor Suspense at all - just use your own state management and pass it as a prop at the root render call, or no state at all.
Your comment might not bring much new information, but I agree with what you say.
Except maybe for one thing: "state" is about observability. That's what justifies the "rules of hooks", not the inplementation details.
I guess people call hooks magic because they look simpler than they are, as if their implementation would rely primarily on closures, which is not the case.
But they play very well with reasoning about closure scoped inside of React components.
Regarding async/await, I'm really pretty much torn. Why can't Suspense wait for any Promise to resolve? Or has this changed by now?
Regarding conditional hook calls: this is really a non-issue in practise, it sometimes even helps with code quality.
Would you initialize an observable property of an object/component conditionally?
I think I'd prefer to avoid that as well.
But, React implements the core component tree rendering logic via a single `while` loop that iterates downwards. That's flat, logic-wise, whereas the tree is nested.
Meanwhile, React already had similar behavior for its error boundaries, where a thrown error in a component would get caught by the component rendering logic and it would "bubble up" to the nearest error boundary.
So, they opted to implement Suspense's mechanics the same way, except that instead of throwing an error, you throw a `Promise`.
Meanwhile, React components on the client have always been pure synchronous functions. No `async/await`, no generators, and thus no support for returning a promise from a function.
With React Server Components, React now supports `async function` components _on the server only_. They've done some prototyping with support async components on the client (and I think even briefly accidentally had a couple releases where that technically was turned on), but there's some kind of either technical issue or release planning issue that's kept them from building out that support for client components (possibly support for `AsyncContext` in browsers).
What I don't understand is why there's no easy way to conditionally render a subtree depending on promises contained in the props of the top-level component.
Kind of like a guardian HOC triggering a re-render whenever all props promises resolve.
Is it because that would be a one-time thing and not synchronously reactive?
Seems like people reimplement or reuse this kind of thing all the time, but often using useEffect (transforming side-effects into state, losing deterministic rendering).
You're right that this would get ugly really fast with nested suspense boundaries though.
You can pass _any_ JS value as a prop to a child component, and there's nothing special about any of that as far as React is concerned. You can pass a primitive, an object, a Promise, an AbortController, a DOM node, anything. All React cares about is "here's what gets passed into the child".
Suspense has a couple key bits of behavior: it needs to let _deeply_ nested components trigger the _nearest_ Suspense boundary, and it also needs a way for React to know when the async behavior is done (ie, the promise resolves) so that it knows when to re-render. Throwing a promise is certainly unusual conceptually, but makes sense in light of those constraints.
(I'm probably misunderstanding what you're envisioning and didn't manage to answer it properly - feel free to clarify with an example if you'd like!)
I was thinking something like
<WithPromises asyncProp={promise} asyncProp2={promise2}>
{(resolvedValues) => children(resolvedValues)}
</WithPromises>
But that's already possible to do yourself I guess, but better in different way.E.g. not using a function prop as "children" etc.
Many libraries also provide nice and clean interfaces to provide async state (e.g. react-query) and then of course there's good old useEffect.
I think I see why they use a different approach.
Guess I'd mainly just love a streamlined API for data fetching built into the core.
Right now it's a lot better to just have async code outside of react change prop values.
Love the section on client data fetching in the react docs though.
I think it’s reasonable to wonder why they’re implemented in such a strange and brittle manner as that. Vue needs no such weirdness, the composition api just uses normal scoping in JS.
It’s really nice. It started in php as xhp in the late 2000s and was later ported to JavaScript as jsx.
Once you learn the basics, as long as you stick to them you can write code so fast / solid.
I haven't bothered with anything beyond hooks, so maybe that's why I'm content.
Good. JavaScript sucks.
Having recently worked on two legacy apps -- one ASP.NET, one Angular -- it made me deeply appreciate React and how far UI tech has come. Phenomenal work from the React team on more optimizations via this compiler. Good article too.
- Preact (like react, but tiny)
- Solid (like react, but tiny and without the stupid re-rendering, or the stupid rules of hooks)
- Lit (like react; but tiny, based on the browser-native web components, and with a class-based api)
Considering that React has a virtual dom, a separate templating language (jsx), and now also a compiler, I would not call it simple.
"that's it" does not paint the full picture. Yes, UI = function(state) is very convenient but the way React implements it inverts the problem. Now instead of having to figure out ways of updating everything you have to go through numerous hoops to get only the things you need to update.
> export default memo(ExampleComponent)
That's literally the point of React Compiler
I ended up rolling my own state management that lets you just write a normal JS class with normal fields and methods and use that for accessing and modifying your state. No reducers, atoms, stores, etc... Just a JS class for your state and logic.
I've got the feeling that I can render anything I want the way I want with React (even 3-d worlds!) but the only framework that comes close to what my old systems could do is
https://mobx.js.org/README.html
and I cannot understand why MobX isn't more popular than it is. (Worked fine for a websocket-based app to control my smart speakers, it was trivial to make it so adjusting the speaker volume through the mobile app or buttons was reflected on my web app. Never tried a bigger app)
Our bottleneck was that we have so much data, making it all observable up front led to slow initial load times and high memory usage. We now make things observable on demand which has eliminated a lot of that.
I miss it when working with languages other than JavaScript.
1. They can easily provide derived data without manual memoization.
2. You can provide custom setters that do more than just assign values.
3. Your UI only re-renders the parts that reference fields that changed, not every component.
4. Atoms compose nicely etc.
Putting plain JS objects in context/state and passing them around only really works for simple, small apps. Beyond that you need more powerful tools. I also like Zustand for this.
Zustand looks a bit better but it is still very different from normal JS. Nobody writes code like that normally. Why can't we have normal, boring code for state management?
https://github.com/Facepunch/react-class-model
7 stars on github? Class objects with decorators? No thanks.
What's wrong with decorators? Technically not needed but I like things being explicit.
React has yet another templating language.
Sounds like we agree that JS isn't a shitty programming language, but that's hardly unique to react.
If React decides to create a new instance of a component because it thinks a parameter change happened all the state in that component can get dumped which can have effects like a bunch of selections in a combo box disappearing.
From your example, I think you've assigned bad keys, or forgot to assign keys (for which the linter will scream at you)
I would prefer a Javascript, DOM, Browser, HTTP, HTML and CSS expert to any framework specialist. These frameworks are ultimately a means to an end. The fundamentals matter the most. If you are intimate with fundamentals, everything else is just a walk in the park to map the fundamental knowledge to the frameworks approach or implementation.
I am not building Vue apps. I am not building React apps. I am building apps, and those frameworks happen to be one building block.
So if you know how to build killer shit for the web, I would not consider your lack of React knowledge to matter at all. You can pick that up quick.
People keep telling me I just need to lie on my resume but I really don’t want to do that.
I left React years ago for greener pastures for a reason.
It might just be that, because they aren't married to the system anymore, they have a larger perspective than the people who can't see out of the system they are in.
The reality is probably somewhere between your and my extremes.
JavaScript has language level coroutine features like async/await or yield/yield* and we have seen libraries using these features to implement direct style algebraic effect. For example Effect[2] and Effection[3]. You don’t need to memoize things if the language runtime can suspend and resume your functions instead of throwing exceptions and rerun them.
The first is replacing useMemo with some inline code that does the same thing, so, as far as I can tell that literally saves just one function call to useMemo but none of the actual work it does? How could that possibly have a performance impact?
The second is de-inlining (or whatever the proper word is) map(x => foo(x)) to save function definitions on each loop. I don't understand why a "React compiler" would be able to do this if a JS JIT compiler can't, what guarantee does it have that a JS compiler doesn't? This should be done by V8 or not at all.
Even though you have a whole paragraph on the cognitive load of an extra compilation step at the end of your post, which is bang on, IMO you don't even come CLOSE to explaining why this trade off is worth it.
I've been telling people for nine years that React is the best of the declarative DOM libraries because it has a simple line-for-line transform instead of a big complicated compilation step. It looks to me like we're just throwing that all in the bin for absolutely fuck all.
Welcome to the last 5 years of React development. React was "finished" with 17, but it's now a zombie project being filled with increasingly absurd feature sets serving as a promotion tool for bored Meta devs.
I don't explain why the trade off is worth it because I'm not convinced the trade off is worth it. I'm explaining because devs using React will need to know, I'm not trying to convince that it's the right choice.
In practice this leads to ignoring the rule, disabling the rule with a comment (and potentially forgetting to add vital dependencies when the function is updated), or adding a bunch of unnecessary noise to the dep array.
There absolutely might be cases that can’t be solved, so I’m hoping to break out of my bubble and learn what they are!
useEffect(() => { doSomething(someState) }, [otherState])
An empty dependency array means that the effect runs only once on mount. Same with memos and callbacks -- the value should remain stable. Here's a real world example of how I populate some state based on url query params:
https://github.com/Naught0/combinator/blob/master/frontend/s...
I may end up using a more robust routing solution to keep in sync with query params if I ever want to spend the effort, but this is a naive solution that works alright.
A more simplified, generic example could be:
const [foo, setFoo] = useState();
useEffect(() => {
setFoo("bar");
}, []); // The eslint rule wants setFoo here despite the value being stableI can't judge the React compiler yet, but in general, compilation steps don't cause cognitive load if they work. How many different levels of interpretation and compilation does V8 do? And do you care? Do you have to? No, and that's the point. Whether React Compiler reaches the same level of "it just works", we'll have to see.
This part is key to the OPs objection. They’re saying that V8 is a very capable optimizer (which it is), so why can’t it optimize the things React Compiler optimizes? It’s a fair question.
> compilation steps don't cause cognitive load if they work
Personally I disagree. Any source code transformation adds cognitive load because my original source code now doesn’t match the source in the browser. Source maps are the traditional answer to that but if React is rewriting logic (like getting rid of arrays) you’re not going to be able to maintain a 1:1 source map.
Because capable != exhaustive? All compiled languages can benefit from further optimizations. I genuinely don't understand the issue here. Help!
I don't know the answer - I don't use React personally.
You're missing the whole context about how React is a library with a huge enterprise sponsor (Meta) who uses it to build some of the biggest web frontends in the world (Facebook etc.) where most of their engineering talent, like most enterprises, will trend to be juniors in order to save money.
Juniors will not naively get the dependency array for useMemo correct, period. With enough juniors banging away on enough PRs, especially if PRs are not reviewed by the most senior talent to save time, this becomes a problem at scale. The need to introduce extra compilation steps, especially when a codebase the size of Facebook already has (I'm sure) distributed build caching, is more or less immaterial compared to the savings of getting more PRs to be correct when first raised for review.
I'm not too thrilled about Vue or svelte either. But there are differences. Vue is more "relaxed" with state and effects, and svelte (and Vue 3) go the compilation route to keep the illusion a bit better. Both can get nasty quickly, with complilation hacks playing significant part.
For a current small project I started with Vue (mainly for Quasar) but switched to React due to Vue magic starting to break. Now useMemo is starting to get unwieldly, so probably switching to SolidJS or Preact/signals.
BTW not sure what you mean with the useMemo - I built very large applications in React (hundreds of complex dynamic financial analysis pages) and didn't have much problems with it - could you go into detail please?
React is a fundamentally different paradigm from the event-based DOM, but it tries to do a (leaky) stateless abstraction on it. You can have event-based APIs with different kinds of organizations. E.g. SolidJS is an event-based framework with component organization.
useMemo requires explicit tracking of dependencies, and it requires a lot of handholding to update just what is needed. Of course you can do practically anything with it, but it can get tedious and error prone.
We lap up all the open source and ideas that the FAANGs of the world put out and presume they are awesome because...it is hard to get hired there? People've heard of them? They pay a lot?
It's all political bullshit instead of actually doing the hard work of evaluating the idea and implementation.
As an old school web dev, to this day I cannot understand how multi-megabyte JavaScript code blobs (after gzipping!) became "normal" and acceptable.
2. You haven't seen Angular lately, have you? It's on 18 now. 15 and 16 really trimmed things down.
Svelte's is about 2.1KB last I checked. Can React get below 40KB?
Just created a fresh Vite+React app and rendered a "hello world" component, and the resulting output is:
dist/assets/index-uoOveHrm.js 142.66 kB │ gzip: 45.76 kBThe package size I linked is specifically the `react-dom.production.min.js` bundle that is used on the client side. The `react-dom` package does include _separate_ bundles used for server rendering, but that whole React client bundle will get included in your app, and it does _not_ tree-shake at all.
(To be clear I _like_ React, but it's best to be accurate about what happens here.)
There's 3 different flavors of `react-dom` for use in the client (dev, prod, profiling), and then several variations of `react-dom` for use _on the server_.
The client bundles don't include any of the server functionality.
_None_ of the bundles are treeshakeable at all, because A) they're shipped as CommonJS modules and not ESM, and B) the way the React library is written and architectured as a whole. All of the reconciler logic is intertwined and unshakeable, so even if React did suddenly switch to shipping ESM modules instead of CJS, it would still end up as the exact same bundle size.
Sure, I'd lose access to the React ecosystem, but I'd also gain access to the vast ecosystem of vanilla JS libraries without having to write/use a wrapper, so that's a wash. (bind:this is awesome!)
useMemo has the problem of having to specify dependencies manually and making fine grained updates difficult. Letting go of the unworkable pretension of effect-free components would solve these self-inflicted wounds, like is done in e.g. SolidJS.
- memoization sometimes crucial for performance.
- passing objects and functions around is common.
- you cannot compare objects and functions for semantic equality
If you wrote larger react apps you almost certainly had to useCallback at some point so that memoization worked, the compiler fixes that.
Whenever you construct an object or function the react compiler memoizes on their free variables so pointer equality is sufficient.
Though I do think the compiler caches too aggressively, even if there are no free variables and hoisting is sufficient or escape analysis shows it is unnecessary for semantics.
The phrase you are looking for is "loop-invariant code motion". https://en.wikipedia.org/wiki/Loop-invariant_code_motion
Just use Remix. The database/url is your state.
Messy state management in Client-Side react is a problem of the late 2010s
I admit that the parens and braces mess causes significant readability issues (though less than the tag soup). E.g. coffeescript gets rid of this problem very elegantly.
But, when coffeescript is discussed, it's "syntax doesn't matter, just use ES6" but when it's JSX, syntax is crucial.
It's sunken cost fallacy, Stockholm syndrome and FUD.
<MyThing><div class="thing--contents">"Hello"</div></MyThing>
versus const div = document.createElement("div");
div.innerText = "Hello";
div.classList.add("thing-contents");
const wrapper = thingFactory(div);
Aren't we? It just adds up very quickly is what I'm saying.Example using Lit and Template Literals:
html`<div class="thing-contents">Hello</div>`;
Example using only web components: shadow.innerHTML = `<div class="thing-contents">Hello</div>`;I was just saying that I do occasionally do things entirely by hand in Javascript with createElement, and it works for a quick thing but it is actually definitely a thing that warrents bringing in some alternative.
const div = e("div", {className: "thing-contents"}, "Hello")
Or with coffeescript div = e "div", className: "thing-contents", "Hello"
Or with coffeescript and simple Proxy wrapper: div = @div class: "thing-contents", "Hello"1. Doing "myElement.innerHTML = `...`" is sufficient for the low-hanging fruit that most jsx gets used for.
2. I also have a `Fluent()` function that returns an object with attrSet/attrRemove/classAdd/etc functions, which each also return the same object. It lets me do something like:
var el = Fluent('div')
.attrSet('disabled')
.classAdd('waves btn btn-small')
.idSet('MyNewElement')
.push(Fluent('div')
.attrSet('...'))
.attachTo(document.querySelector('#SomeParent');
So far, I'm not liking the second one much, and the first one is really hard to compose, but they're both useful for when you don't want a build-step. el = @div disabled: true, class: 'waves btn btn-small', id: 'MyNewElement',
@div ...Walks like an XHTML duck. Talks like an XHTML duck. It's an XHTML duck despite the JS interop and the lack of DTD preamble.
It makes the language substantially more complicated, with additional corner cases (e.g. className and htmlFor). For what I see as solely matter of (bad) taste (being used to XML syntax for defining elements, and abhorring extra backticks?).
The pseudo-HTML is not far from JSX in the hackiness, but at least it's embedded in HTML.
(Possibly relevant source/proof: my TSX-based library supports the HTML shortcut names just fine, like:
`class`: https://github.com/WorldMaker/butterfloat/blob/cb9498354a7fb...
`for`: https://github.com/WorldMaker/butterfloat/blob/cb9498354a7fb...)
https://facebook.github.io/jsx/ is the primary home.
> are there multiple transpiler implementations?
Yes. Off the top of my head:
Babel: https://babeljs.io/docs/babel-plugin-transform-react-jsx
Typescript: https://www.typescriptlang.org/docs/handbook/jsx.html
esbuild: https://esbuild.github.io/content-types/#jsx
> is it standardized at all?
In terms of well documented, yes. In terms of a TC-39 standard accepted as a part of JS and intended for browsers to consume? No. Unless you count how much it borrows from E4X [0] which was an optional part of the "lost version" of JS that was EcmaScript 4, then "sort of".
One thing I didn’t say in the post: I’m very much a fundamentals-first kind of dev. I teach fundamentals in my courses.
I don’t think you should use tools like frameworks or transpilers/compilers without understanding how they work, because debugging is always better when you know what the tool is doing for you, and it makes it easier to use the tool. React is a particularly leaky abstraction that benefits from understanding it under-the-hood.
You need strong fundamentals to understand how things work.
Seeing JSX and reading about all the complexity in this post makes me hope I’ll never use React again.
BTW I do like JavaScript.
In the past 2 or 3 years, they just "gave" up, turned it into the biggest most bloated framework in the frontend area while the official Web APIs in the browsers evolved so much that React is actually completely useless and now it is completely useless with a compiler.
I'm wondering if that was actually the reason they pivoted to this Frankstein? The loss of relevance as a frontend library.
Anyway, I jumped off the bandwagon and don't have a say in this fight anymore. But I'm doing my part advising every Junior Developer to not make the mistake of choosing React today.
I don't like react much, but I surely hope you advise them on ways to organise state management and rendering with good ways to track event listeners.
Anyone advising juniors against learning React is shooting them in both feet, not least because of the demand for React developers.
Strong foundational knowledge in HTML and CSS helps as well. Still amazes me how many folks put onclick handlers on div tags and freak out when margins collapse.
1. https://survey.stackoverflow.co/2023/#section-most-popular-t...
With an experienced and top-notch developer, anything is possible in any language or framework. For the other 95% of devs out there—especially on a team where the lead doesn't keep a strong grip on the reins—React turns to mud awfully fast. But it's mud with an extensive ecosystem and tons of inertia helping it succeed.
Consider jQuery. It was extremely popular once; was super influential to the point that some of its apis were adopted by the browser; is completely irrelevant now; but is taking a long time to die. I've been wondering whether React is destined to repeat this trajectory.
The bloat in the ecosystem of (mostly) abandoned packages is unparalleled. With the bloat comes the need to tree-shake, bundle, compiling, things that are not needed in modern web development but takes a good amount of the dev cognitive load when developing a React app.
There are great alternatives nowadays that don't come with the bad recent decisions of React: Solid, Lit, even vue or svelte.
Do I recommend someone learn React to develop a web app nowadays? No, it's not worth it.
I'm not preaching to you, but to the juniors reading the thread. React contributes significantly to current JavaScript fatigue, and supporting it so juniors can get a job isn't in my or their best interest.
I’m discussing career advice. Something junior developers look to senior developers for. If your advice to your juniors results in them having fewer employment opportunities then you’re not doing a good job as a mentor.
> Do I recommend someone learn React to develop a web app nowadays? No, it's not worth it.
In what way is it not worth it? The majority of front end dev is done in React. Of course it’s worth it, even if it’s shitty.
You'd be putting yourself at a disadvantage in the field (employment wise) not learning at least some basics. Especially if you're just trying to get your foot in the door. It's already hard to 'break in' but even harder in this hiring climate.
Smaller, cleaner, and faster code while still focusing on dynamic interfaces? Svelte or SolidJS
Focus on server-side HTML rendering with little to no browser dynamism? PHP (It really is quite nice now.)
Focus on server-side HTML rendering with some browser dynamism? HTMX + server framework (PHP, Django, Go templates, etc.)
Getting a job as quickly and easily as possible, especially at larger companies? React or Angular (You're a cog in the machine, but cogs often get better paychecks to deal with multi-megabyte code blobs.)
Can't decide between code elegance/performance and finding a job, so you're willing to compromise a little on both? Vue
React is hobbled by the need to retain backward compatibility, but it is boosted by sheer scale and a solid underlying philosophy, design, and engineering.
Someday, we will probably find a fundamentally better philosophy for UI frameworks, but I don't know yet what it will be. React replaced its MVC predecessors with a functional approach, the Flux model.
AFAICT, if you pick any of the others options you'll have to refactor your site every 3-4 years as all the tools bit rot. One won't run on the latest version of node so you upgrade node. Now 2 other libraries fail and require an upgrade. Now another library fails because it's not compatible with those upgrades, repeat. You thought you were just going to fix a bug or add a small feature that would take you a few hours but now you've got few days/weeks of refactoring before you can even start.
WebComponents are the future (and the present) of web development.
Now is WebComponents prime time. Libraries like Lit have just a little bit of code that makes the DX of webcomponents be top notch. Some people like to use TS with it but what I love about lit is how powerful it is with just vanilla JS and no compile/bundling/building phase. Development directly in the browser, sane stack traces and faster than anything I've seen.
Lit code is similar to what React was until the 16 version.
If you're messing around and having fun, sure learn some hipster frameworks.
If you want to have highly saught -after skills years from now, basic web Dev with most popular frameworks on top is by far the safest bet.
I agree with you about knowing a bit of most popular frameworks tho, they are quite interchangeable and very often adopt each others famous features. The biggest advantage of choosing Lit as your main tool is that it is the one that is more integrated with web components that won't go anywhere, anytime.
Turns out, it's quite trivial to publish react and angular components as web compenents.
That completely took the value prop away from lit, and in terms of DX and library support, the other frameworks won easily
I'm not saying don't use smaller frameworks. I'm saying the safest future bet is learning the big ones
I feel like from an employability perspective I shot myself in the foot, but I also dislike both of those frameworks. So maybe I should just quit being a front-end developer and try to retrain as something else.
That said, transitioning to some backend or infrastructure focus never hurt anyone. It's good to see problems from different perspectives and roles. No one ever got fired for knowing too much SQL.
If you already have fundamental web dev knowledge, you'll be able to switch between them easily.
That's why I like Remix. It's react + web fundamentals. I'm happy to jump into a Vue, svelte, angular project. It's not all that different
I always disliked the it’s a library not a framework debate. Isn’t any library a framework, technically?
/s
Also, try calling App and see what happens if it uses hooks.
App() will never be called.