React Holy Grail isn’t so … Graily

Zac Tolley
Scropt
Published in
8 min readApr 23, 2018
Disappointment

In the past year I have been working on a new project for a back office application that is form heavy. It started off simple, but the user interface slowly included new interface components that, though simple looking, became quite complex and the original aim of using progressive enhancement went out the window in favour of new ways to do things.

Giving up on progressive

For a while I wrestled with the whole topic of ‘Progressive Enhancement’ vs ‘React JS and JS based views’. Actually I wrestled with it for a long time. At that point it was more of an ethical thing but the benefits of new and shiny React won through. The promise of a simple render function and concentrating on just managing state is tempting and brings benefits. Components can be tested on their own and proven. Code is clean. I actually didn’t use redux. I kept telling myself I would move to it later as the code I was building needed to happen fast, but in the end I never really had a reason for it.

So I moved the project so that all forms were rendered in react on the client side and smart forms were easier, though the complex components ended up with pretty much the same line count.

All seemed good, and then…

This is where I’ll admit it.. The new and shiny took over.

Anyone who has worked with react knows about ‘universal rendering’, or ‘isomorphic applications’. The idea is simple, react is JS (Javascript), node is JS, the same code that works on the browser can work on the server. I set about rendering a forms initial state on the server and further renders for state changes on the client. Form pages loaded almost instantly, like traditional pages, and were so much more ‘expressive’.

Then I really got carried away, why have half the pages rendered using Nunjucks and the other half rendered in JSX. I changed all the views to be JSX based and split the application into a number of major ‘chunks’, so you didn’t load the whole thing at once.

And you know what, the pages render real fast, moving between sections is fast and my code is really clever. I have achieved the Holy Grail, so why do I have a nagging doubt?

It turns out that doing a cool clever thing isn’t always right. I felt like a person who has spent months trying to pick the perfect gadget, then the second they get it it just sits in the corner gathering dust.

Whats so bad then? I can sum it up in 3 things:

  1. Ugly cut and paste code
  2. Hard to debug complex problems
  3. Transpiled server code

Ugly code

React code itself is fine, the render state idea is the best bit and code structure ends up being clean, self contained, testable and a set pattern so teams can work together, but when you start to bring react router and server side routing/rendering into play it can get ugly quick.

The problem is the ‘glue’ code you need to create. In your server side controller you need to create something that instantiates react router, get the initial properties for it and also has to integrate with your session management or csrf server side stuff. To do this you need a few thing in different places. In your controller you’ll probably end up with a piece of code that looks like this

const token = req.session.token
const csrfToken = controllerUtils.genCSRF(req)
Router.match({ routes: routesConfig, location: req.originalUrl },
(error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
renderProps.params.token = token
renderProps.params.referrer = req.headers.referer
loadPropsOnServer(renderProps, {}, (err, asyncProps, scriptTag) => {
winston.error(err)
const markup = ReactDom.renderToString(<AsyncProps {...renderProps}
{...asyncProps} {...req.query} />)
res.render('layouts/react', { markup, scriptTag, csrfToken, bundleName: 'company' })
})
} else {
res.status(404).send('Not found')
}
})

Here’s the thing, I took this from an article I found and seems that this is ‘the way you do it’, plus I had to improvise to get some of the extra values in like token and referrer. If you asked me to describe what this does, I’m not entirely sure, and when a new starter joined I pretty much said ‘it works though’.

Now in addition to this you end up with code in your top level app too that does:

static loadProps (context, cb) {
const params = context.params
const backLink = getBackLink(params)
companyRepository.getCompany(params.token, params.sourceId, params.source)
.then((company) => {
cb(null, { company, source: params.source, sourceId: params.sourceId, backLink })
})
.catch((error) => {
cb(error)
})
}

This is separate from the constructor, so it feels like the code is a bit all over the place and not very clear what goes where.

Then the final piece of ‘fun’ is the fact that the components I have use a ‘repository’ to go fetch data from the datastore, the challenge is this code is different depending on if it runs on the server or the client. I did manage to find a clever way around this though:

const replaceCompanyRepo =
new webpack.NormalModuleReplacementPlugin(/companyrepository.js$/, 'remotecompanyrepository.js')
...

This goes into your webpack config and it used in the plugins section. When it builds a client side copy of code it uses one JS file, when it builds a separate copy of the code for server it uses another.

This is all over the place, ugly and you need to know how it works. When you come back to this in 6 months you might struggle.

I would take simple easy to read code over clever code any day.

Hard to debug

This is sort of linked to the ugly code and the transpilation but also goes much deeper.

We had problem with data from the backend server breaking something. Because it was running on the client there was no ‘logfile’ for anyone to look at or monitor, and devops didn’t know how to bring up the developer tools in Chrome. When a developer had an idea roughly where in the code the bug might live he wasn’t sure if he should debug the server or the browser. Even after all this it takes time to remember where to even start debugging, and remember the ugly code above?

When you finally decide you need to debug the server because the issue is there, well then you are in another world of pain because you are running transpiled code and even though Webstorm supports keymaps the breakpoints just don’t map quite right sometimes and you’re back to using console.log, plus you are using babels layer on top of node.

Server transpile

Debugging transpiled code is one thing, but then wanting your server to restart when you make changes and have those changes build for client too, well its just not fun, plus technically the code you debug isn’t the resulting code.

I had a new starter come along who I needed to get the platform running and start making changes but the whole thing was hard to describe and there were many things you had to remember. This all distracts you from writing simple, maintainable code.

Who benefits?

So you did this clever thing, you are holding the prize. Now sit down to some user testing and you know what, the user has no clue how you did it and doesn’t really care, they just want the screen to come up and work.

You do get to look clever though, tell other developer friends and it sounds good in an interview. All the while you have this big hunk of code that new people cant get their head around and god help you in 6 months if you need to change it. Oh and React Router changed a bunch of times and that mass of code you don’t quite get won’t work with it and you don’t know why.

So what did I do?

First version was released, it has gone live and it’s working but now I’m working on the next version that adds alot more screens and I’m moving back to the progressive enhancement, server rendered approach.

We have revisited the screen flows and those screens that needed the clever Javascript stuff actually work better when redesigned to be simpler.

Some of the other big changes in approach have been:

Tabbed based navigation

Previously switching between tabs was done in JS, hiding and showing page sections. The code now has a page per tab, this has actually reduced the work that the controller needs to do and broken it up into multiple parts so each can do one thing and do it well and are easier to test. As far as the user is concerned this has actually increased speed, the controller does less, switching tabs is as fast as the JS version was.

Simpler forms

By making the forms simpler and plainer, users actually find them easier to use and they come up faster. The whole form is submitted and re-rendered with error indications when needed, but with modern browsers there is no flicker, the browser seems to just render the change so it feels solid.

View and Edit are different pages

Previously the read view was shown and the user would press edit which would hide the read and show the edit. Making these two separate pages made the controller code less complicated and the flow easier to handle.

The result

So now there is less code, it’s easier to read and it’s way easier to maintain or throw new people at. Code is run on the server without need for a transpiler. The whole build process, starting, stopping and debugging is just a ton easier. You just don’t have to worry about as much stuff or have to explain things over and over to new people.

The users are oblivious to this all of course, but now you have more time to concentrate on what they want and not reading articles to try and be clever, or feel like you aren’t as cool as the other devs.

And you know what, I don’t wrestle about progressive enhancement anymore, I’m proud that all the pages can work without JS.

Next time the front end starts to get complicated and needs clever JS I will try harder to find a way to make it work without JS or I’ll push back on the design and question the need for it.

Would I ever use react

Yes. React allows you to do things on the client you just cannot easily do with any other approach. I love reacts structure and way of working, though I’m not a fan of JSX. But yes I’d use it, but what I would not do is render on the server. So sure, build your react to drive an API and serve it up static so it renders on the client.

A year on, what has changed…

This article was written at the start of 2017. Back then NextJS was new and wasn’t ready for me to try it.

It seems that times have changed, ALOT. Next JS and React Router are now much more SSR friendly and the sites promise you can write code that works on client and server that is not that much more complex… sounds too good to be true.

When I find some time I’ll give Next JS another go with Redux and see if it is seamless or if it still feels like you are fighting the frameworks.

--

--