React, or no framework?

by Jon Wyatt

This post is personal opinion rather than representative of the Tes development team in general. If you asked three other developers at Tes you’ll most likely get three different answers (and maybe three more blog posts after this one!).

React has been the JavaScript framework of choice at Tes for the last couple of years, recently paired with Redux. Dozens of apps have been built in a variety of styles.

Opinion across the whole development team has varied, from ‘Reactify the world’ to ‘use only if strictly necessary’. Even among the React fans, there wasn’t complete agreement on how to use it - for client-side rendering only, or isomorphic.

The good

After my first few months of using isomorphic React I was convinced it was a silver bullet, more than just a solution for client-rendered Single Page Apps, as Angular.js had been. The ability to render on server and client with the same code gives the fastest user experience; SEO crawlability by default; plus lets you progressively enhance functionality like routing and form posts. Keeping the bulk of processing on the server gives more predictable, scalable performance, fewer browser-specific bugs and makes it easier to reuse components for Accelerated Mobile Pages.

React makes it trivial to apply fine-grained updates to the UI based on state changes, rather than replacing a whole block of HTML (e.g. if a user has focus in an input, they won't lose that).

React components encourage modular code and composition. Redux’s container components make it easy to inject required pieces of state into any level, saving you having to pass data down through a hierarchy of components. This, and the possibility of drawing from a library of reusable components, can make React / Redux suitable even if you only need to render on the server, as an alternative to Handlebars for example.

The component / flux architecture encourages a clean separation between presentation, data fetches & transforms. Or you can make a component mostly self-sufficient, a sealed unit that is responsible for its own data fetching e.g. a 'favourite' button that can be embedded in any app.

The bad

React isn't the best of all worlds - it does bring some baggage. You need to judge whether your application is worth the overhead.

Transpiling code

You can choose to use React without JSX. You could also choose to lace your tea with washing up liquid.

Attempting a comparison with no preconceptions, which example conveys the hierarchy more cleanly and concisely?

<div className="my-component">
    <div className="my-component__profile">
      <a className=“my-component__link” href="http://engineering.tes.com/profile" rel="nofollow">
        <img className="my-component__profile-image" src="http://engineering.tes.com/profile.jpg" alt="My profile" />
      </a>
    </div>
  </div>
or

React.createElement(
    "div",
    { className: "my-component" },
    React.createElement(
      "div",
      { className: "my-component__profile" },
      React.createElement(
        "a",
        { className: "my-component__link", href: "/profile", rel: "nofollow" },
        React.createElement("img", { className: "my-component__profile-image", src: "/profile.jpg", alt: "My profile" })
      )
    )
  );

For me anyway, using React without JSX isn’t a sane option. Unfortunately that means you have to transpile it to JavaScript every time you make a change, which slows down your workflow.

Reducing the amount of transpilation is key to having a comfortable experience with React. For example, can your tests run against precompiled code rather than compiling it again separately?

Also, splitting 3rd party libraries into a separate browser bundle will let you leverage long-term caching but also speed up your transpilation step - only your application code that changed will be recompiled.

Page weight

A bundle containing React, ReactDOM and Redux weighs around 54Kb gzipped and minified. If you’re using ES6 constructs like promises in your browser code, you’ll also need to include the corresponding polyfills. Add the most basic application code on top of that and you’re heading towards 80Kb before you’ve added your first button click handler.

This is the best-case scenario, if you’re disciplined about bringing the minimum of 3rd party code into your browser bundles.

Browserify and Webpack make it painless to import any common.js utility library. A Moment here, a Lodash there - you don’t feel a thing, but your mobile users will.

That’s not the fault of React of course, but buying into the React ecosystem and toolchain can make it easier to get into that situation.

Another consideration - isomorphic React requires that you embed a snapshot of your data store in the page itself, to allow the application to ‘rehydrate’ when it starts up in the browser. Again, if you’re not careful and dump large parts of an API response into your store, your HTML response size can easily balloon.

Learning curve

Personally I found the learning curve with React and Redux way easier than with Angular, which I’d struggled with. However there are definitely hurdles to clear, including the component lifecycle, Flux principles, plus all the associated tooling like Babel. To achieve something basic with React and Redux takes a lot more complexity, boilerplate and wiring than simple Handlebars templating.

The React world is also a moving target. React’s own API has stabilised after version 15, and Redux has emerged as the defacto Flux implementation, but a range of libraries around them have sprung up. Will your Redux code be twice as clean if you use Sagas? Or Reselect? I have no idea, not having used either yet, but others swear by them.

I’m generally skeptical of adding new libraries unless there’s a really good reason - they could be deprecated next year, blown away by even newer developments, and you’re left with a dependency that might not keep pace with changes to the React APIs.

A healthy development team should be able to deploy most developers to most tasks, but adopting the React ecosystem can mean a much higher bar to jump before a developer can contribute effectively. Even after that, it’s an ongoing time cost to keep up with the pace of change and assess new techniques and libraries.

Integrating with non-React code

A React component demands control of the DOM within its container. If you initialise another widget’s markup within that container you have to be prepared for it to be wiped when the component re-renders. You can re-initialise it from the component’s lifecycle methods e.g. componentDidUpdate, but will need to ensure it doesn’t re-initialise unnecessarily. For this reason it can be a struggle to integrate non-react components like Uploadcare and Disqus.

Final thoughts

Personally I’m still a huge fan of React and would seriously consider it for use cases other than the classic ‘Single Page App’, but weighed against the above overheads.

As mentioned, some of the adverse effects like page weight can be minimised if you’re aware of the pitfalls and monitor for them.