Please Stop Reinventing JSX
gist.github.com
gist.github.com
I confess that I missed the whole migration to SPAs because I’d stopped doing web stuff altogether for a while when it came into fashion. I’d love to be convinced that this really is way better for reasons I’ve failed to appreciate so far.
If, in fact, that’s true.
Were I forced to write a website today, it’d still end up looking similar to a traditional Django server-side rendering system with pages, and deep links, and all that.
Step 2 - You push a few client-side templates as data, so you can do interactivity.
Step 3 - Your server side framework (not surprisingly) has no support for client-side templates, so you add hack over hack to get the more and more complex you need here and there.
Step 4 - You move those few components into a client-side framework, and interact with them through hacks that your server-side framework doesn't support.
Step 5 - Hell, why insist on using 2 entire GUI frameworks? You move all components into the client-side one and ditch the hacks!
Step 6 (bicycle on the floor and you hurt) - Your server-side framework doesn't interact well with the client-side one, so you move into JS everywhere.
This progression still seems unavoidable, but the web standards evolved almost as much that there's a light visible at the end of the tunnel.
It's funny how we have come full circle. We are now doing so much on the client that we are often reaching client-side resource limitations. In a typical pendulum swing that our industry likes to go through, many people are saying that the answer is server-side rendering. Many younger developers who weren't working in the industry prior to the shift towards CSR even repeat this sometimes while thinking that SSR is some new tech that's going to save us, unaware that SSR has its own trade-offs and that CSR was itself initially seen as a solution to scalability pains.
2. Programmers prefer using programming languages over templating languages. Jsx allows you to mix js with xml and also generally supports better composability than templates (another thing programmers prefer).
3. Some programmers like to use jsx for normal websites with ssr under the spirit of "use what you know".
https://bun.sh/docs/runtime/jsx
https://docs.deno.com/runtime/manual/advanced/jsx_dom/jsx/
You have to sub the JSX factory, eg React.createElement with a library that spits out html instead of building a DOM tree.
It's not hard to do, for example I created this tiny library to play around with async server-side components. https://github.com/dsego/ssr_jsx
I think for Deno you have their Fresh framework which does JSX components https://fresh.deno.dev/.
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "null",
If you want something a little more powerful then hono/jsx is a decent choice [3].[1] https://github.com/developit/vhtml
However it seems like this idea has been taken to extremes and is being overused, and applications that do this for GUI elements are probably just badly designed.
I don't understand the whole SPA movement either and it feels like it just doubles the amount of work needed to maintain anything. I reach for Django/server-side when I can but unfortunately, not many folks appreciate those thoughts.
I can see some perfectly valid use cases but there are times when it's just not needed.
That's perfectly reasonable, but JSX et al aren't for building websites. They are for building applications that run in the browser.
The idea to use it for completely static content came later, mostly because people didn’t want to learn two different frameworks.
But also, that level of interactivity is pretty much expected now even from layman end users whether they realize it or not.
Requiring a full page refresh is very limiting when building out web apps, for a bunch of reasons. There are a few sort of “hybrid” approaches out there that work reasonably well, but the reality in 2024 is that nearly any web app that sees even moderate levels of success is going to grow in complexity to a place where SPA is the right choice. So we have a bunch of SPA experts building apps that they expect to need SPA levels of complexity…you can do the math.
Of course, I prefer Redux the hard way over how a lot of these SPA tend to get written in practice.
React is easy and intuitive. No templates, and I can change the HTML however I want in response to what the user does. Not that I do fancy things or enjoy single-page apps, but even if you have that, deep-linking is easy.
But, React is easy and intuitive to use for the use case that makes the least sense. Once you get to complex state, I'd argue that React doesn't help much. There are a lot of different tools and ways to manage state, but the way React takes over rendering often leads to weird and confusing bugs, as the 'declarative' syntax is a *very* leaky abstraction.
so, how different is React now from where React was in 2016?
The JS/SPA developers I worked with then would refuse to touch it or any of the other frameworks. We just wrote our own callback functions to handle the interactivity and data updates.
Purely server rendered apps tend to have much slower interactions and exhibit weird behaviors so it is valuable to be able to do some stuff on the client.
The only reason that server-rendered apps are "slower" is because people don't think about what they're doing, and leap right to 100% client-side rendering. Very few things actually need the latency guarantees of client-side rendering.
The client-side part doesn't make data transfer any faster.
Also think about what actually has to happen when you interact with a server rendered app vs a client rendered app. Once the client app is booted you don't have to pay network latency, html parsing, FOUC etc on every interaction as you would with a server rendered app.
It's quite literally a technical argument, and not "dismissive" in the slightest. My first sentence tells you why it's a strawman: there is no such thing as a "server-rendered" app. The framing of the original comment was black-and-white, when reality is (and has always been) shades of gray. Webapps have, since basically the earliest days of the internet, a question of where you draw the line between server-side and client-side rendering. Leaping to SPAs is just as lazy and thoughtless as trying to do everything on the server.
> There are lots of client side interactions that do not need to be blocked on data from the server. Popup menus, modals, collapsible lists, rich text editing etc.
Yes, of course. And if you implement your popups, modals, accordions and whatnot via asynchronous server calls, you are doing it in a silly way that guarantees bad performance.
There's a middle ground. For example: you can trivially render top-level dynamic elements (like menu content) into a SSR page. It doesn't require an SPA, yields imperceptible UX latency, and allows you the ability to do things like SEO without crazy infrastructure.
(Oh, and you don't need to "boot the app" in the browser. That's latency too.)
You pretty clearly laid out what you meant by server rendered in your original comment:
> Were I forced to write a website today, it’d still end up looking similar to a traditional Django server-side rendering system with pages, and deep links, and all that.
Nowhere did I advocate leaping to SPAs for no reason. I simply made the case - and stand by it - that building with the same set of technologies client and server side leads to more flexibility and less code.
Re: the comment about booting the app, I think that is one of the strongest arguments in favor of server rendered apps, which is why I qualified my comment to "interactions", which happen after the page loads.
I lived through the server-rendering-only days of the original Hotmail client, and the php-plus-jquery spaghetti era. They were terrible for developers and terrible for users. Going backwards would be a huge mistake.
I didn't write this. You're confusing me with someone else.
I would guess easier reactivity and state management on the client. You don't have to do things the jQuery way in manually having to sync your DOM to your data on changes/api data.
And SPAs still have pages, and deep links, and all that.
Which actually is what make them bearable as frameworks.
* https://nextjs.org/docs/pages/building-your-application/rend...
The array pattern is much more useful when data is more complex and has multiple properties, nesting, etc.
Still, one of the big benefits that can't be ignored is that jsx looks ugly and the array looks much cleaner.
If you were to do it as the post author states, it would look like this (bad)
<ul>
<li><Link active={'/home' === window.location.pathname} to="/home">Home</Link></li>
<li><Link active={'/about' === window.location.pathname} to="/about">About</Link></li>
<li><Link active={'/contact-us' === window.location.pathname} to="/contact-us">Contact Us</Link></li>
</ul>What if you want to render your navbar differently depending on the context? Eg. on desktop it's a flexbox at the top of the page, and on mobile it's an <ul> in the hamburger menu?
Uhm maybe ...use ...context?
<Menu isHamburger={isHamburger}>
<NavLink to="/a">A</NavLink>
<NavLink to="/b">B</NavLink>
<DownloadLink file="/c.txt">Download C</NavLink>
<InfoBox>All services up-and-running</InfoBox>
<NavLink to="/signOut">Sign out</NavLink>
</Menu>
Heck, you might not even need a context to accomplish this.It is not about avoiding iterating through an array either. The objective is the readable description of the user interface. It is to improve orientation and maintainability.
Some of the worst JSX related code I've seen has involved people trying to build up arrays for data like this.
I really don't mind JSX in terms of syntax. That said, I've used ASP.Net Webforms, VB.Net (XML Literal Notation) and ActionScript (e4x XML Literal Notation) and others. Even developed a UI toolkit similar to what React became (a decade before), but only supported Netscape (e4x).
That array list might be generated somewhere else, with much more data and used for multiple purposes.
But the pattern the author argues against isn't necessarily bad. Certainly not an "anti pattern" or against "the spirit of the law".
Take their own example. An app may very well render a certain set of "nav items" to JSX in multiple ways. In that case it probably makes sense to define the common parts separate from the JSX -- otherwise you'd have to maintain each list of nav items in JSX separately.
We have a general guideline on this: if the template configuration is static and/or not on the server, it should be plain JSX. This rule has worked out great for us. I think I'm always wary of layers of indirection and needless abstraction, which is why I'm able to catch patterns like this early.
But an argument like "ok, well then, you're still wrong, just now in the opposite direction" strikes me as contrarianism for its own sake.
This JSON-based description...
const entries = [
{_type: "nav_link", to: "/a", displayName: "A"},
{_type: "nav_link", to: "/b", displayName: "B"},
{_type: "download_link", file: "/c.txt", displayName: "Download C"},
];
...is the same as this JSX-based description... <Menu>
<NavLink to="/a">A</NavLink>
<NavLink to="/b">B</NavLink>
<DownloadLink file="/c.txt">Download C</DownloadLink>
</Menu>
...but with extra steps and in needlessly novel markup. The extra steps being something like... <Menu>
{entries.map(entry => {
switch (entry._type) {
case "nav_link":
return <NavLink to={entry.to}>{entry.displayName}</NavLink>;
case "download_link":
return <DownloadLink file={entry.file}>{entry.displayName}</DownloadLink>;
default:
throw new Error("Invalid type", {cause: entry});
}
})}
</Menu>
These are more abstractions on top of abstractions that on their own would have sufficed. <li><Link to="/contact-us">{ localize("contact-us") }</Link></li>And of course you are then feeding back those descriptions into needlessly abstract JSX but the primal error was not realizing you are describing components in a bespoke manner.
const menu = [
{_type: "nav_link", to: "/a", displayName: "A"},
{_type: "nav_link", to: "/b", displayName: "B"},
{_type: "download_link", file: "/c.txt", displayName: "Download C"},
];
...is the same as this JSX-based description of the user interface... <Menu>
<NavLink to="/a">A</NavLink>
<NavLink to="/b">B</NavLink>
<DownloadLink file="/c.txt">Download C</DownloadLink>
</Menu>
...but the first one comes with extra steps and novel markup.The whole point is to be able to convert JS data structures (or often plain JSON) into HTML via JSX
Data and representation are different. I may be using the same array to display different components
If that component is not going to be reused anywhere or tested in isolation, absolutely inline it.