Hacker News new | past | comments | ask | show | jobs | submit login
Please Stop Reinventing JSX (gist.github.com)
43 points by kevmo314 3 months ago | hide | past | favorite | 89 comments



Remind me again why we decided to do all this stuff client side? As a server template pattern that seems perfectly reasonable. I don’t see why you’d push that out to the client though, along with a healthy dose of code to make sure it generates identical output in every browser.

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 1 - You do it all server-side, and some people start requesting interactivity.

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.


in step 1, did you mean server side?


Yes I meant server-side. Got in time for editing it. Thanks.


Historically, client-side-rendering came about at a time when server-side compute was expensive. You have to remember what CPU limitations were like prior to around 2010. Website load times were often slow, and scaled poorly with traffic, because anything dynamic needed to be rendered before it could be served. Caching was common fare but load spikes when caches expired were common and personalized content (along with an industry trend towards more and more personalization at the time) added a lot of additional expense.

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.


Yes, and wasn't mobile a big part of the shift to client side? If mobile users lost their connection, for whatever reason, CSR was thought to be a good remedy for the user experience (UX).


Initially mobile network bandwidth was very limited and so CSR + single-page web apps was a way to cut down on full page loads. Lean RESTful endpoints could be optimized to only return what the client actually needed in a given context in order to update runtime state without having to fetch and re-render anything superfluous.


1. People are building actual applications with client side interactivity on the web now.

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".


Is there any SSR web framework besides NextJS that lets me use JSX?


Bun and Deno support .jsx and .tsx files out of the box.

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/.


If you like JSX as a templating language and you want to generate simple html strings then just use vhtml with TypeScript [1][2]. You have to import the h function from vhtml in your tsx files, and then update your tsconfig file so that TypeScript knows what to transform the JSX into:

  "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

[2] https://github.com/developit/vhtml/issues/31

[3] https://hono.dev/docs/guides/jsx


Yes, would like to know too. I know that the latest version of React features more SSR capabilities of components without (I think, but don't know for sure) the requirement of NextJS.


React has always had the ability to do SSR without Next. You can call renderToString on the server to generate html strings, and then hydrateRoot on the client to pick up where the server left off.


Thank you.


My naive view is that this type of thing is done so that the server returns structured data from an API call, and then the client renders it however it sees fit. If the client is a browser, then it uses HTML. If the client is curl, then it pipes it to jq, etc.

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.


In many cases you could have a server-side client for your API calls, with a little client-side code for features that actually require it.


Yeah, it helps to have one backend and then each client doing its own presentation for the web browser, phones, TVs, tablets, etc.


More people to do more building which increases team size and inflates ego? :)

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.


> 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.

That's perfectly reasonable, but JSX et al aren't for building websites. They are for building applications that run in the browser.


I agree - but everyone seems to believe that their web site is actually an application.


The clue’s in the name. React is designed to react to events like clicks and input, and to keep state. It’s a replacement for jQuery, not for Django.

The idea to use it for completely static content came later, mostly because people didn’t want to learn two different frameworks.


You’re right that it’s much, much more difficult to do client side SPAs!

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.


As a fan of SPA apps and React in particular, I will say they're overused, and something like HTMX is probably a better option at least 2/3 of the time.

Of course, I prefer Redux the hard way over how a lot of these SPA tend to get written in practice.


How do you server-side render something like a chat app? You'd still need a fair amount of client-side JS, right, unless you want to reload the page every time someone hits send. I remember old sites that worked that way.

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.


A chat app is one of those relatively few cases where React makes some sense.

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.


Web apps with dynamic behavior are pretty common. I've written some of those, but I also found React easier just for mostly-static webpages that need somewhat flexible layout. You can do most of that with CSS instead, but it tends to involve super specific knowledge and hacks. Vs having a convenient, generalized way to change the DOM at will.


>> React is easy and intuitive to me.

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.


I'm not primarily a frontend dev and am probably not doing things the trendy way. I started using React and RN in 2018, I adopted the Hooks syntax when it came out in 2019, then I avoided the newer frameworkey stuff like Next.js. Also didn't mess with Redux.


The main benefit is the flexibility to seamlessly move this logic from server to client and vice versa without rewriting all of your code.

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.


This is a straw man. There's no such thing as a "purely server rendered app", and never has been (except maybe in the pre-JS days of the internet). At the end of the day, all webpages are produced by a server, and rendered by a 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.


And when you have to go to the server to get data, it's often slow with a client-side 'app' anyway.

The client-side part doesn't make data transfer any faster.


Right, exactly. I almost wrote something about that, but stopped myself. It's a very common pattern for people to move everything to an SPA for "latency", but then treat the data channel to the browser as if it's a app-server database connection, which is the same thing, but worse.


Calling this argument a strawman is pretty dismissive and is not a technical arugment. 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.

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.


> Calling this argument a strawman is pretty dismissive and is not a technical arugment.

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.)


There's clearly something else going on here because I am having trouble following this argument.

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.


> 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.

I didn't write this. You're confusing me with someone else.


> Remind me again why we decided to do all this stuff client side?

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.


You can now have the best of both worlds using something like HTMX or Hotwire Turbo. SPA-like navigation with fast page loads, partial updates, streaming updates, but also HTML on the wire and minimal (client-side) JavaScript.


You can't have offline apps in this way though


certain apps benefit from immediate low latency behavior that can only be done in the browser. if you were to build a tool like google docs, for instance, you could not do that with server side rendering.


But this article is about creating HTML before it gets to the client. We clearly couldn’t make Google Docs entirely on the server, but I feel like if we’re creating a list of navigation links on the client from a const list, maybe it’s time to rethink some of that.


JSX can run perfectly fine on the server. But it can also work client side, so you get a more versatile and more consistent codebase.

And SPAs still have pages, and deep links, and all that.


A generation lost that never used server frameworks with component libraries.


I did and it honestly sucked


Yet Next.js and React Server Components are racing to replicate the experience...

Which actually is what make them bearable as frameworks.


Replicate the experience of HTML templates? I looked at their SSR example*, and it doesn't look at all like the old ways. It's primarily client-side with the ability to selectively add in SSR, which seems reasonable.

* https://nextjs.org/docs/pages/building-your-application/rend...


Only for those that never used component based SSR.


The author chose a contrived example with that simplistic nav with just two properties.

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.


An easy example which is used in the most basic menus is selecting the active link.

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>


A simple solution is to create a component that does what you want which retains the benefit of being explicit.


Which is even more effort, and isn't better than the original solution.

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?


> What if you want to render your navbar differently depending on the context?

Uhm maybe ...use ...context?


So you'd rather write twice the code and use React contexts for this simple feature just to avoid iterating through an array?


Why would it be twice the code? Depending on what it is exactly that you want, you might just add 4 lines of creating a context, passing a value to a provider, checking the value of that context. And in return you get a most readable description of the user interface:

  <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.


Not too strongly opinionated on this one either way but I'd say the array looks cleaner when you can actually see the array; when it's derived from a function or is using much more complicated data it can be quite a bit harder to pull useful info from and can be quite a bit easier for a junior to mess up.

Some of the worst JSX related code I've seen has involved people trying to build up arrays for data like this.


Not to mention, that you might want/need your routing for more than just a navigation display.. but for determining routing, named routes and other links in an application.

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).


Thought the same thing.

That array list might be generated somewhere else, with much more data and used for multiple purposes.


I agree with the idea of not maintaining an intermediate representations of things that serve no useful purpose.

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.


This is not an issue. It is a stylistic question ("Do I express this in JSX, or in JSON?"). Only, this question has already been answered for you.


I completely agree. I've had to "unteach" this from most React engineers that I've worked with/hired. I agree with the reasons given by the article, and I'd offer an additional reason it's so terrible: you can use this pattern with almost everything. Eventually you end up with a codebase that's really hard to reason about as you're mostly just reading the arrays and not the JSX.

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.


The <li> is at a different level of abstraction. <li> can have DOM attributes applied. For instance, we might want to add [title] to all the items. If you "unrolled" to JSX, now you have to make n changes instead of one.


Turn it into a component then. That is how you abstract in JSX.


I mean cool, fine. But are you arguing for less abstraction or more? The original post was about unrolling everything and declaring inline. In a sense, you're now going in the opposite direction by adding another layer of abstraction. I'm ok with it either way. I think all of the above might be reasonable designs in particular contexts.

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.


JSX is a language for describing user interfaces. The author argues against inventing your own JSON- or JavaScript-based languages for describing user interfaces and also feeding all of that back into JSX.

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.


My web dev experience is old enough to vote by now, so the answer might be obvious, but how does this work with localization? Would you have a copy of the JSX snippet for each locale? In that case I can see some benefit from separating strings from structure.


Rather than embedding "Contact Us" directly, I guess you would use this.

    <li><Link to="/contact-us">{ localize("contact-us") }</Link></li>


In general yea, just use XML to describe your static layouts. But as soon as the collection is dynamic or rearrange-bale, make it a component that maps an array of data and consider virtualization.


This isn't really "reinventing JSX", it's just over-abstraction too early. This can be an issue in all code, not just JSX.


It is reinventing JSX in that you are really describing components in JSON.

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.


"reinventing" is a strong and less clear word for "over-abstraction" in my book. Nothing was reinvented in this, and the very example the author gave to prove their point could make sense if, for example, you don't know the menu links ahead of time. Like if they are coming from a database table, this would be totally valid. If we do know the menu links ahead of time then there's no point in abstracting it.


It is reinventing JSX in the sense that this JSON-based description of the user interface...

  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.


I suspect the author has never had to make a site in multiple languages.


Disagree totally....

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


There’s nothing wrong with this pattern. It would be like calling JSON or YAML an anti-pattern. Sometimes you want data in that format that a parser then parses. Normally if it’s a dynamic sidebar that comes from the server. I appreciate the author striving for simplicity though! It certainly can be over or under applied like most things in software engineering.


The Antd react UI library used to do exactly what the article recommends. Then they went through a lot of work and rewrote everything to work in exactly the way he recommends against. Part of the Antd argument is that they are able to get better performance and maybe better Typescript sanity by passing in an array of objects instead of dealing with a JSX tree.


Maybe so but you are not writing a general purpose UI library. You are writing a specific purpose UI component. You do not need to handle everybody's use-cases and edge-cases. Maybe you are justified in adding on to the complexity. Only maybe you are not!


I don't get the "reinventing JSX" part, it's not what's happening here. JSX allows you to render a list by iterating over the elements of a JS array, using directly the .map method of the Array object. It's one of the main features of JSX. I'm very confused by the title, is it just clickbait?


That's not even a feature of JSX. The curly braces are a feature of JSX. Inside is arbitrary expressions. .map is an array method that exists independently. You can have just about any expression inside the curly braces. This is not part of the JSX specification, but the host language.


Very unconvincing article that I thought would be about something else entirely. What's the problem exactly?


It tries pretty hard to tell me what's wrong with the first example, and I still don't see what's wrong with it.


"unroll your loops" is not something i had on my bad advice bingo card for today


1. I agree, Keep it simple. 2. The reason is in the SOLID principle. 3. In Django, we have a `reverse` function to generate the full URL to a page by passing args/kwargs. that's a good practice if the project's structure changes in the future. but again. keep it simple.


Nice article! This is exactly me -- I looked into our codebase, and the array and map example is what we have! Changed it now :-)


And now when you want to make a simple style change you need to update every single link


Exactly, which is the entire reason the array pattern exists, and yet the article forgets to mention that entirely.


Still sounds like a component?


You're arguing for more abstraction. The article is arguing for less. You're going in the opposite direction.


No, not really. I'm arguing for putting a list of static html tags in a component instead of generating html from a static data structure.

If that component is not going to be reused anywhere or tested in isolation, absolutely inline it.


What? That's not true. You can use a custom `Link` component...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: