React views for Express and HTMX!

Published : Saturday 7 September 2024

Creating a React (TSX) view engine for Express for use with HTMX ...

Okay, I know what you are thinking, but hear me out ….

Before I go on, I should say I’m a hobbyist developer (of 20+ years) and know enough to be dangerous. Do your own due-diligence.

Background

I’ve been coding and building websites since just after the dotcom boom, back when we didn’t have fancy libraries like jQuery never mind modern frameworks. Cloud hosting was in its relative infancy, and managed services cost a small fortune. I was writing ASP and PHP, using Microsoft Access (yes, really) for databases, and raw-dogging JavaScript to help small businesses get online.

Despite browser compatibility hell, it was a relatively simple HTTP world and what I loved then, and still appreciate now with MVC patterns, is that the server handled state management, sending HTML with just a sprinkling of JavaScript for basic utility.

If you know, you know
If you know, you know
(Click for full resolution)

Since then a new Javascript framework has come along every 5 minutes and in more recent years the masochists have settled on React. React introduces a whole new pattern to learn, messes around with state management, and creates unnecessary headaches. All that pain just to submit a name and address form. Why? It’s way too complicated for 90% of use cases*.

* Do not trust any statistics you did not fake yourself – Winston Churchill (allegedly)

HTMX and React

Lately, I’ve been using HTMX. It takes me back to the good old days—simple, fast, and it works without overly bastardizing web standards. Sure, you need to do a bit more on the server, but that’s a plus in my book.

For my next project I’ve settled (for now) on an Express/HTMX stack but I was still frustrated with views. I’ve used EJS and love Handlebars however neither gave me the javascript coupling, type safety and binding I longed for. I’d been using Sveltekit with SSR (Server Side Rendering) for some time, although it was a bit of a love hate relationship. Then I had a lightbulb moment, surely if NextJs is a thing I could use something like React on the server for simple views?

I quickly found that this was possible and some people had created libraries to do this, but they had not implemented them as I imagined. I also wanted a project I could perhaps release as a npm package as I had never released one publicly before.

TSX React Views

In short, my requirements were to keep my solution clean and simple, ignoring the complexity of React and using the bits I like on the backend for my views, similar to how I use Handlebars:

  • Specify a default/master layout for the application
  • Override or remove this at the route level
  • Dynamically specify the view at the route level
  • Automatically expose locals to the view
  • Pass custom properties to the view
  • Strongly typed as much as possible

Implementing

It actually turned out to be quite straightforward. I learnt a lot looking at how others had been dynamically rendering React and then tried to reduce this down to my requirements.

Most of it hinges on using React to create elements and ReactDOMServer to render the HTML string.

let element = React.createElement(view, props)
const html = ReactDOMServer.renderToString(element);

There is some basic logic to figure out what layout to use and constructing the React element to render, but that’s about it.

I decided I needed a custom renderReact method on the express response, rather than trying to use res.render because I wanted it to be typed and didn’t want to break existing render logic in applications.

declare global {
      namespace Express {
          interface Response {
              renderReact: <P>(component: React.FC<P>, props?: P, options?: ReactResponseOptions) => void;
          }
      }
  }

This wasn’t quite as strict as I wanted due to the optional props?: P, but I had some issues downstream rendering empty views and this was a quick fix.

For initialization I wanted a simple helper method that allowed me to pass the express app and global options to the view engine and would wire up my render method.

export function initializeReactRenderer(app: Express, options?: ReactGlobalOptions) {
      app.use((req, res, next) => {
            res.renderReact = getRenderReact(res, options);
            next();
      })
}

For the layouts and views I wanted to keep this really simple. Layouts would need to have a children element to allow it to render the child view, but that is about it. Then allow locals to be passed to layouts/views and for views to receive properties as well.

Arguably contexts is the way to pass locals around, but this broke my “keep it similar other view engines” rule. I settled on passing them as properties, and while they needed to be specified in the view definition (for type safety), I passed them automatically in the view engine.

To assist with this model I created two default property interfaces that could be used for layouts and views.

  export interface ILayoutProps {
      children : React.ReactNode;
      locals: Express.Locals;
  }

  export interface IViewProps {
      locals: Express.Locals;
  }

This means locals can be typed and destructuring works as expected. Layouts and views end up looking like this:

export default function Layout({ children, locals }: ILayoutProps): ReactElement {}
export default function View({ locals }: IViewProps): ReactElement {}

And while there are some other bits and pieces around this, that was fundamentally it.

Next steps

I’ve been using this code for a while and recently pulled it out of my web app in to its own repository and published an npm package which has been an interesting learning experience and hence this blog.

For the purposes of publishing the package I added a bit more validation, some tests, documentation, a quick HTMX demo project, and that’s pretty much it for now. I could add a bit more , but there isn’t more I need it to do for me as of today, but I’m open to ideas.

Get tsx-express

You can install the package from npm:

npm i tsx-express

Documentation, node package and git repository:

Dan's Blog

Information Technology, programming, health, fitness and photography enthusiast.

  • Not a writer.
  • All views are my own.
  • Offence is optional.
  • Do your own research.

Post by tag