(disclaimer: I'm the author of mithril.js, a framework that can be used w/ JSX)
My initial reaction on this is that this looks like a nice (if minor) DX improvement. Not having to type out `React` when it's seemingly not referenced anywhere makes sense.
The underlying signature change is also interesting. If you need to support IE9, then doing `jsx(name, {children})` is going to save a lot of array slice calls that would otherwise be needed for the `React.createElement(name, attrs, ...children)` signature.
On the plumbing side, there's something that bothers me about this change: given this is seemingly a small life quality improvement, it affects a lot of tooling infrastructure (babel, typescript, etc). From a purist perspective, this has a "cancerous" feel (as opposed to being "encapsulated"). This doesn't mean this is a "bad" change per se, but I do feel like it feeds into the idea that "software these days is way too complex"(tm) that has been popping up recently.
> From a purist perspective, this has a "cancerous" feel (as opposed to being "encapsulated")
I have the same feeling about much of the React ecosystem (edit: though I'd say "tightly coupled" rather than "cancerous"). The other day some folks were discussing the idea of building a new UI backed by a new GraphQL schema. We started talking about the possibility of delivering the UI independently of the GraphQL schema by using an existing REST API, and the idea was shot down as the GraphQL client we're using on the frontend is highly coupled to the UI components. Changing the data source later would have been a highly intrusive change.
I totally understand the benefits that a GraphQL client provides, but it feels like a serious architectural smell that changing the data layer is such a wide-ranging change in a web app. Wrapping a data-source in an API and being able to change the implementation without the rest of the application being impacted is really compelling, and I'm not convinced it's something worth trading-off.
As a counterexample to consider, I would suggest that a client-side web app is coupled to any remote data source intrinsically by a latency-sensitive connection over the network. As soon as you have enough data, you need to defer portions of it and load them on demand based on user interactions. So now the ways in which data was or wasn't factored in a way that mirrors the app impacts it.
Now, there are tradeoffs we can make to deliver an end state to a user: we can normalize and incrementally hydrate the data shown in the client and increase visible jank in the process, or we can realign the app's experience and/or data source around each other.
Decoupling UI components from business components is of course valuable, but that's different from taking that a step further and decoupling business components from the data source. The latter is a significantly more challenging proposition.
Also, there's some fuzziness here in what we mean by decouple. REST vs SOAP vs GraphQL as transfer layers can be abstracted away as implementation details, but I'd place a higher emphasis on the shape of the data and how it has to be traversed in the client. If you have a paginated list, for example, and you only want to load it once, but you need to display both a summary and load it incrementally, things can get complicated as more functionality is built out from that starting point, regardless of transfer layer.
As a counter-counter-argument, one could argue that there's coupling to the database and one might as well then write db queries from their single file components. One stack (meteor) took that idea of trading loose coupling for productivity to the extreme but then suddenly found itself in an awkward position when react and npm exploded in popularity (and Mongodb lost mindshare).
Surely something else will eventually displace react and/or graphql, just as previous stacks and paradigms have been overshadowed. But then we go back to my original assertion: refactoring away from the legacy of the tools we no longer like is decidedly going to be more complex if we chose to sacrifice loose coupling in favor of something else (performance, initial dev speed, what have you)
Case in point, esbuild looks like a very promising replacement for some web plumbing. But if there needs to be extra collaboration to support this there, other stacks can take the opportunity to dash ahead (e.g. vite) while the react team is busy collab'ing with all these compiler projects.
> but I'd place a higher emphasis on the shape of the data and how it has to be traversed in the client
There is something incredibly satisfying in rendering an entire UI experience from a single GraphQL payload, without having to build intermediary data structures to stitch it all together from multiple endpoints.
I agree that that tight coupling is a smell, but it's also not required; the Apollo (GraphQL) tooling offers it and some of the documentation encourages it for new users, but not using it is a first-class option too. That's the paradigm around which (for example) the entire application for which I recently co-led development is built. We didn't have any trouble using it, and while replacing GQL with REST would still be a heavy lift just due to the fact that it's a highly data-driven application for which we also maintain our own backend, we wouldn't need to touch the UI to make that change.
I don't love that the docs tend somewhat to encourage the API option with tighter coupling between UI and data, and the Apollo documentation is in general pretty lightweight by my standards. To criticize on that basis is fair. But it isn't anything like forced, and to write off GraphQL or React as "cancerous" on account of someone having made a less than optimal decision in how to use them seems premature.
The unencapsulated thing is exactly the feeling I got. I am most confused with others' code when there is implicit imports or declarations of any sort. "Where did this come from??? Why is this working???"
I had the same thoughts about React hooks although perhaps I don't correctly understand the motivations. Class components have extremely clear, compartmentalized lifecycle functions, and now the advice is... make everything a function-scoped closure? Let redux do some opaque context sorcery to update your components?
React (and others) hit the paradigm right on the head the first time, IMO. It's change for change at this point, which is fine I suppose.
Both the old and the new transform are built on Babel, so Babel is involved. Both transforms participate in TypeScript typing, so TypeScript is involved.
Not that you're not well aware of both these points, but I do feel like the point at which to scruple over "software is too complex" comes somewhere well before the point where JSX is happening at all, and I'll admit it's a little confusing to see them apparently in the opposite order here, especially put in such strong terms as these.
After all, there's a distinction to be made between essential and accidental complexity. I'd argue that what both these transforms do is to abstract away a lot of the latter so that the former can be more easily seen and managed, and I think they do a pretty good job of it. (The rest of React, I'm not so sure about, some days...)
There's a subtle aspect of this change that may not be immediately apparent. One that, imho, significantly regresses JSX as an independent syntax outside of React (i.e. makes it much more coupled to React than before):
JSX is an SGML/XML-inspired syntax, and SGML/XML are languages with 3 dimensions of element definition: names, attributes and children.
Internally, React elements are represented in 2 dimensions, with children managed as a "magic" attribute, with the reserved magic key "children" (this is for performance reasons I believe). Before now this has always been an implementation detail of React. JSX is flexible enough that other libraries could provide an element factory method with a fully featured 3-dimensional internal representation. This change removes that flexibility.
JSX has always been SGML/XML-inspired, but not SGML/XML compliant, so nothing is changing there.
And the thing is, even if it were, what a parser/compiler does to represent a language is its own completely independent thing.
At one point I had to use XML in JS (back in the day, haha). I parsed the XML and represented each node as a single object, with `__name` and `__children`. They were "one dimension" in my representation, but also literally a compliant XML parser— more compliant than JSX.
> a parser/compiler does to represent a language is its own completely independent thing
This isn't really true of JSX; I'd suggest giving it a try to see exactly how the system works.
To summarize, there are 3 parts of this discussion:
1. The JSX syntax (part of the JSX spec.)
2. The JSX transform (part of the JSX spec. & what parser/compiler must represent the language as)
3. The template function implemention: 100% independent and can represent nodes any way they want
In your comment you're imagining a system where 2 & 3 are coupled, whereas this isn't how the JSX world works. With JSX, 1 & 2 are coupled, only 3 is independent.
> 2. The JSX transform (part of the JSX spec. & what parser/compiler must represent the language as)
Ah, ok I see where this misunderstanding stems from. See, this is not part of the spec. With JSX, there's a very popular transpiler (Babel's), but it's by no means the only one or even "the official one". The spec actually lists three, and mentions [1]:
> "These are a set of transpilers that all conform to the JSX syntax but use different semantics on the output: [...]"
JSX is most commonly used with React, so if you're making a JSX transpiler it makes sense to make your default output work with React. This is by no means part of the definition of JSX though or a spec requirement.
From the post, it appeared to me as if this change was made, specifically, to remove the dependency on (importing) React. Are you sure that this results in making JSX dependant on React, instead?
Firstly, you're right that the main content of the post is re: not needing to import React. My comment was just pointing out that they have additionally changed the factory signature (an unrelated change).
Secondly, I should point out that JSX is currently not dependent on React. You can use JSX now without React, either via Typescript (built-in support) or via the custom `pragma` config key in babel's JSX plugin.
The change described in the article makes it unnecessary to import the full React lib into each component file when using JSX with React.
`importSource` is just a newer version of `pragma` (I mentioned above, listed on that page below `importSource`), and works fine right now without this new transform.
> The change described in the article makes it unnecessary to import the full React lib into each component file _when using JSX with React_.
`importSource` is more than just the new `pragma`, it also handles the fancy auto-import magic. This change makes it unnecessary to import your own custom lib (not just React) into each JSX file.
Ah ok, I get your meaning now. Yeah, it saves the need for the line in source code, but doesn't affect the generated code either way (you still need to include React somehow if using it, or whatever alternative if not)
You linked to the issue which appears to list the benefits of this. This is a stepping-stone change to: (from [0])
- Deprecate "module pattern" components. (ed: I don't know what this is)
- Deprecate defaultProps on function components. (ed: Presumably, allowing normal param default values instead)
- Deprecate spreading key from objects. (ed: Presumably, performance)
- Deprecate string refs (and remove production mode _owner field). (ed: unclear how this is related)
- Move ref extraction to class render time and forwardRef render time. (ed: presumably to make refs "just work" and not require the forwardRef rigamarole)
- Move defaultProps resolution to class render time. (ed: performance? not sure)
fwiw under old JSX you were always able to change the JSX pragma to any other library you wish, so i'd rate this no change in "degree of dependency on React" tbh.
I don't think I understand your objection. Can't a library trivially convert between the two representations if it wants to in its implementation of _jsx?
Having children as a property of the parent isn't an implementation detail of React -- it's just a mapping onto Javascript, where objects have properties, and there's no independent notion of children. The new transform makes things more like Javascript, which seems like a good thing.
EDIT: (since you updated your comment, some of my original reply no longer applies)
Part of the elegance of JSX is that it maps conceptually to the 3 dimensions of it's intended output structure (usually HTML) rather than being restricted by direct mappings to the 2 dimensions represented in JS objects.
--- original below ---
Both transforms work "like javascript"; I'm not sure how the new one is any different in that regard.
While I do think it's a little nicer to not have to import React, they could've done that without changing the factory signature.
Furthermore, I think the value of not needing to import React is slightly overstated. If you're using React & JSX, you'll be importing React in your application at some point regardless (so importing in the JSX component file shouldn't add weight). If you're using JSX without React, you don't currently need to import react anyway (as transpilers provide configuration options for factory methods: e.g. via Babel's react-jsx plugin's `pragma` config key)
more than not needing react it looks like the new transformation is react-agnostic, so that all libraries that consume JSX can use the same transformation and put all application specific functionality in the _jsx function
The new output has bundle size improvements (per the announcement) but each call to the pragma function now instantiates a new Object. I'm curious if there are performance implications in very large React applications.
I guess every application will be different, so maybe, but this comes along with a large push to improve the performance of React's factory functions so it would be difficult to measure objectively.
I don't understand your contention that the new transform removes the flexibility of how other libraries could provide an element factory method?
Do you mean that before JSX could be compiled to createElement and some other library's createElement could be used? If so I would ask how often does that actually happen?
I would assume that any library that wanted to consume JSX would have their own JSX parser and compile to whatever structure that library wanted?
on edit: I guess you went through that in the other comments, and your worries are at least partially handled by the changes to Babel?
> Do you mean that before JSX could be compiled to createElement and some other library's createElement could be used? If so I would ask how often does that actually happen?
Yes, this is quite common practice. Many non-React libraries support this, but it's also extremely easy to use with your own templating function (the implementation can be as little as 1 line long)
> I would assume that any library that wanted to consume JSX would have their own JSX parser and compile to whatever structure that library wanted?
Nope, this isn't how JSX works. For example, React does not contain a JSX parser. JSX syntax is transpired by a build tool: currently the two most popular build tools are TSC and Babel. Both support targeting a custom library or your own custom code instead of React (but only the function name can be customised, the function signature that JSX is transformed to, as discussed in this article, is not customisable)
Ok thanks, goes against my assumptions of how I would implement a JSX consuming library, but I guess there are probably lots of good reasons (performance, less code) why people do it this way and, as can happen, my assumptions probably wouldn't stand the test of actual implementation.
This is definitely concerning given that JSX has made its way into other projects like Vue and Stencil. I'm having trouble following though, what exactly does this limit?
It doesn't really place any hard limits other than reserving the `children` key on attributes as a spec feature rather than an implementation detail.
A 3rd-party factory can still break the `children` attribute out into a separate entity in their internal implementation.
All it does is make the interface to the transform messier, by surfacing internal performance-related implementation details of React. This is code smell and I'd call it a syntax regression, but it shouldn't cause any severe limitations on what you can achieve.
Personally I'll still continue to use JSX transforms with my non-React code once this lands. It's a great syntax.
React components access the children value through the children property of the props object, so it's not just an implementation detail. It's part of the component API.
I think the similarity between the children value and other props is useful, to emphasize how any prop can contain child JSX elements. There's nothing special to the children value besides some syntax sugar. You can pass JSX elements to any other prop too.
Is the React team still employed by Facebook? I’ve noticed that while many of them are quite active in their own names on social media, several of them seem to have scrubbed any mentions of their employment status from their accounts. Are they working independently, or are they perhaps not so proud of who is paying their salary?
Sorry for bringing up this seemingly tangential question, but honestly the only thing I can think of when I see React stuff these days is that Facebook is behind it.
You can see that React development essentially stopped May 24 and then got sort back to normal by the end of June, but it's still a bit below normal levels. I think the cause was that many React devs became fed up with Facebook. I don't know how many actually quit though.
I had assumed the slow-down was a result of resource-drain on two fronts:
1) Major updates of the actual Facebook product - I could see a lot of React resources being pulled into that.
2) Fiber implementation getting bogged down in the reality of implementing a production-ready cooperative multitasking scheme in the browser environment.
I have no evidence for that though - pure assumption.
a couple did. but digging any further goes a little over the line into stalking territory as far as i am concerned, even tho a lot of this is publicly available information. as long as react continues to be in active development i dont think its worth prying too deep into sensitive stuff like this (not likely to get a "real" answer anyway)
On the contrary, it’s outrageous that this industry doesn’t have any journalists willing to investigate the power these people have over our community and who they are affiliated with. Whenever you hear them interviewed on a podcast or anywhere else, the hosts seem to be fawning and giving out fist bumps.
And I’m not so sure Facebook representatives have any ground to stand on when it comes to expectations of privacy.
> but digging any further goes a little over the line into stalking territory as far as i am concerned, even tho a lot of this is publicly available information.
Collecting publicly available information from the web to assess the state and power structure of one of the most widely-used projects on the web is not in any way whatsoever "stalking".
It's not even "sensitive" if it's publicly available.
Yes, and presumably the employees who did quit, quit because they believed FaceBook crossed an ethical line. I dunno, they could have gag order contracts or something keeping them from talking about it, but I bet they'd still like to see FB's ethical problems exposed if they thought they were so bad they needed to quit.
Potentially misunderstanding the question here, but what would it mean for the react team to not be employed by FB? FB owns the repo, and so short of someone forking it and the community deciding the fork is the new source of Truth the react team will always be at FB, right? Or am I misunderstanding the question?
This is a missed opportunity to make JSX more than solely about UI and I'm pretty disappointed about it.
JSX is really a way to describe a hierarchy of deferred function calls independent of their callers. This could be tremendously useful in a number of different contexts, not just UI. What would a state machine look like if the states could be described in JSX? What would rx.js look like if this mechanism was available?
The current tooling makes experimenting with possible alternate use cases pretty awkward, and so basically no one is doing work in this area.
I believe that these changes, particularly the `jsx` and `jsxs` auto import, will make it even harder to separate JSX as a concept from React and React-like libraries. It'll make it harder to standardise JSX, which is frustrating as it's the most supported ECMAScript syntax extension by a long way.
My modest proposal is that a JSX element desugars to a call to a block scoped identifier called `jsx`, so I can do something like this:
Taking this alternative approach would give developers a widely supported syntax extension that could allow for some really innovative solutions. The current path is going to do the opposite.
I understand that there are perf optimisations that React can do if they control all of this. But I feel like they're looking at this too narrowly, from only the perspective of UI. They have the power to open up a lot more opportunities.
TBH this sounds like it should be a new transform, not in the default one.
Speaking for myself, I rather this be an alternative so I can get the best perf on my React apps, but have the option to switch to this when experimenting with using JSX for other things. This is an interesting concept, you should build it (:
Writing an alternative babel plugin to do the transform this way would be pretty trivial, but doing so for TypeScript is not (officially) possible. TS is going to choose one and it's going to be the one React uses.
Also the decision to split React's createElement() into jsx() and jsxs() means that to maintain compatibility all libs would have to do the same, and provide both functions, all because of an implementation quirk in React.
JSX should be a standalone thing. It's not intrinsically React, and shouldn't bend to React's needs alone.
Out of curiosity, why do you prefer JSX to nesting functions? I understand that for HTML you want to meet developers where they are, but that doesn't necessarily apply to other use cases...
I use htm (Hyperscript Tagged Markup) instead of JSX and I love it. It's just plain JS and works extremely similar to JSX: https://github.com/developit/htm
The main difference is that JSX is natively supported by the majority of modern tools while this is a plain string that needs extra plugins everywhere.
Reading the "Improvements over JSX" [1] I don't see anything that makes it worth the hassle (unless you really hate transpilers/tools). Most of them are outdated or not actual "Improvements".
I'm curious why the compiled version is still referencing React code, instead of defining a standard JS data structure that JSX would compile directly into? This would remove any dependency to React itself, with the added benefit of skipping function calls at runtime. It doesn't look like there's much happening at instantiation that couldn't be done after the fact on plain JS objects: https://github.com/facebook/react/blob/master/packages/react...
There were security vulnerabilities a while ago that resulted from people being able to upload React-element-shaped JSON objects in certain places strings were supposed to go. IIRC this allowed for XSS, so React added a special symbol (the $$typeof key) that would have to be imported somehow in order to add it to the plain objects.
I made some noise in a Typescript issue to not add more complexity just for React in the core but offer it as a plug-in but that didn’t go anywhere.
I do feel Typescipt tries to over-accommodate React compared to other frontend frameworks. The jsx code is pretty gnarly in TS and this makes it even more complicated for little gain.
Magic imports are a big no no. The whole idea about import/export syntax is that a module explicitly imports what it uses and other modules can only import what it exports.
I can understand React.createElement -> jsx function. Preact, mihthrill and others use the h function. This already works without new transform.
It seems that with React hooks and special JSX transforms, the authors are trying to prioritize cleverness over simplicity and clarity.
Now every project you have to grapple with the decision “is this automatic or classic?” and what about the next version, is it “super automatic” runtime?
I'd like to see the transform implemented this way:
function foo(props) {
return <h1>hi!</ht>
}
Becomes
function foo(jsx) {
return function foo(props) {
return jsx.createElement('h1', ...);
}
}
Then, the first time a given component is rendered your view framework injects the right value of "jsx" and caches the resulting Component. This means no static dependencies are necessary to write components for libraries that use JSX.
Not having to import React is nice. It took me a bit to realize that it was necessary because the JSX was actually just React.createElement calls. When I teach React I kinda gloss over it because explaining how JSX works is too confusing. But then forgetting to import it gives a confusing error for beginners.
I wonder what plans the React team has for JSX. I liked the prop forwarding style that ReasonReact had, but I guess that's not compatible.
This is interesting; I take the opposite approach, thinking that if I can help someone understand that JSX is merely fancy syntax on top of plain JS function calls, that React won't seem so "magical".
I sometimes see comments like "I hate React's looping syntax" and it's clear that a lot of people get hung up thinking about React as a templating language.
I can see how explaining that extra level of detail could be confusing though, especially right up front, and especially for developers who're simultaneously learning JS alongside React.
I was reading through Crank.js's[1] documentation the other day, and I noticed that all of its examples explicitly set the JSX createElement function with a pragma comment.
Like this:
/** @jsx createElement */
import {createElement} from "@bikeshaving/crank";
import {renderer} from "@bikeshaving/crank/dom";
renderer.render(<div id="hello">Hello world</div>, document.body);
It’s great to see the React team working proactively with some of the major platforms that rely on it. I look forward to seeing the ecosystem co-evolve more in the future.
If you just read HN, you'd think React is losing some of its weight in the community.
However from my experience out in the day to day world (which is largely outside Bay Area Silicon Valley), this is not the case. Indeed, HNers and forward thinking devs here were the first comers to React, largely. I know I was on it pretty early (and walked a way more than once!) but the reality is its just picking up major steam in other places, the so called 'shadow developer' communities, like large .NET shops that when I talk to them at conferences, desperately want to move away from Angular, and React is often a choice (Vue is gaining steam here too. I think this second order of developers will decide how big each get, IMO).
Through that lens, its amazing they're taking these steps to find the balance between 'magic' (auto imports!) and lessening the syntax you have to learn as you grow development. There are so many shops, in enterprise in particular, full of 'shadow' developers most of us never meet or hear about, that are hungry for this kind of thing. Angular has some foot hold here, but Microsoft has been keen on trying to give alternatives, one of which is React (and MS here still has a lot of weight) and I think React will have staying power and growth by making itself more appealing to these kinds of developers. These are the kinds of devs who still use jQuery in 2020 (not that its bad! I swear I don't think that outright), even for new projects. There's a ton of the web ecosystem that has not and does not develop with these things in mind.
I have witnessed this with hooks. Hooks made React more approachable to development teams I worked with because class components had an unintended side effect - Developers made all of the logic encapsulated by the component inside methods - where extracting functions would have been easier to test and been far more modular - simply because thats OOP to most devs (right or wrongly). So you'd end up with 10K line classes that do all kinds of byzantine things with extremely tight coupling.
With hooks, functions become the pattern, and the library (can we just call it a framework already?) really molds the developer into best practices of extracting components and using component composition.
Basically, if you aren't just looking to reach the HN crowd, these changes are great. Thats been my experience so far. React has been growing in popularity at places I've worked and other developers I know see this too (many of which develop at consultancies, PHP/Wordpress shops, .NET shops etc) because of the change of paradigms as well as stability, of course, have made it much more maintainable.
This says very little of the overall web ecosystem, which still has a major churn problem (same for the ecosystem around React, too much churn and breaking changes, over minimal upsides, I've observed)
Since I'm rambling, I wonder what React would look like if Web Components V1 was a thing when it first came out.
I'm seeing a lot more Vue though, more than I did even 6 months ago, and I even saw some job openings for Svelte, in the last month and half (can't remember where though)
would have been nice to see something like, “this could mean className attribute reverts back to standard class and other attrs go back closer to standard html implementation, but that would probably have more far reaching effects that could cause more churn in ecosystem and react related libs
I mean of course, that would be nice. But the thing is modern editors like vscode will automatically convert class statements to className if you copy paste plain html from elsewhere. Don't know about other attributes though.
This may be a silly question, but what are the major JS libraries outside of the react ecosystem that use JSX? My understanding is the technology had codeveloped with react and never totally gotten decoupled?
It was developed alongside React originally, but the syntax definition is now widely used in many other tools. Babel and TypeScript know how to parse JSX and compile it down to raw function calls, IDEs know how to parse it and highlight / autocomplete appropriately, and many other UI libraries are usable with JSX (Preact, Solid, etc).
I'm curious what this means for people who use a vendored React set up. At my work we serve a bundled version of React from our CDN and add it to the window object. Seems like this eliminates the need for that?
from the post: "It will enable future improvements that reduce the number of concepts you need to learn React."
It's unfortunate that they don't expand on this. Is the reduction on the number of concepts simply that one doesn't need to import React? I doubt it. So what does this new transform enable in the future that will reduce the number of concepts you need to learn?
> Other than performance this is also about a long term simplification of number of concepts you have to learn to use React. In particular, forwardRef and defaultProps are no longer something special.
My initial reaction on this is that this looks like a nice (if minor) DX improvement. Not having to type out `React` when it's seemingly not referenced anywhere makes sense.
The underlying signature change is also interesting. If you need to support IE9, then doing `jsx(name, {children})` is going to save a lot of array slice calls that would otherwise be needed for the `React.createElement(name, attrs, ...children)` signature.
On the plumbing side, there's something that bothers me about this change: given this is seemingly a small life quality improvement, it affects a lot of tooling infrastructure (babel, typescript, etc). From a purist perspective, this has a "cancerous" feel (as opposed to being "encapsulated"). This doesn't mean this is a "bad" change per se, but I do feel like it feeds into the idea that "software these days is way too complex"(tm) that has been popping up recently.