Note: My colleague Reinout van Rees posted the back-end side of this story including thoughts on front-end/back-end responsibilities

I was asked to join the EFCIS project team as a frontend developer. EFCIS aims to be the go-to catalog of ecological, physiological and chemical information in the Netherlands.

Image

The project had already started and a prototype was produced using vanilla Django and Bootstrap templates.

This prototype neatly defined the basic interaction model based on which the client agreed and the functional design document had been written.

My main objective was to take the user interface from there to a more complete application.

My recent tinkering with React.js for some smaller projects left me confident enough to try it on this larger project.

Data fetching

Data fetching wasn’t needed for my smaller React projects. I just included the data as Javascript objects.

EFCIS, however, talks to a RESTful API for its data needs.

I decided to just use jQuery’s XHR functions to fetch the data from the API endpoints.

A mistake that hit us quite early was fetching data deep down the component tree. For example, getting a list of parameters just before rendering that list in a modal in some component in some other component.

Doing this seemed logical at first but it’s best to keep fetching as high up the component tree as possible and pass down (parts of) the data as props.

Flux could have saved us some time in the end here, but requires writing lots of boilerplate code. Maybe when I could start all over, I would investigate a lightweight Flux variant.

Transmit looks nice and can talk to REST, but we didn’t have time to dive into it.

Relay.js looks even nicer, but our back-end is just a REST endpoint. Porting it to GraphQL is way outside this projects budget. Also, it was announced while our project was halfway done.

Some tips

  • To keep it DRY, we let Django generate the API endpoint URL’s in the index.html template, and use those thoughout the Javascript code.

  • For querying measurement locations we used GET with lots of ID’s. This quickly hit a limit so we had to resort to abusing POST requests for fetching.

Lesson learned: Decide on a robust xhr data-fetching strategy and stick to it.

Keeping state high

Similar to data fetching, keeping application state should be propagated from components back up to the top-level (app.js) component.

So we use keep state in the top-level component for things like date-range, selected parameters and selected locations, and pass this state to all the components that need it.

In sub-components, we only use setState for compont-internal settings, like ‘is this modal open or closed?’.

This keeps the flow of data easy to follow, a much touted pro of doing component composition with React-like frameworks.

Componentize but don’t exaggerate

Component composition for me is the best thing about React, because it enforces a one-way data flow, aka ‘prop-passing’. Splitting a project into a component tree greatly helps with reducing the mental bandwidth required for development. There is however a balance to be considered: cutting everything into a sub-sub-sub-component misses the point of separation of concerns.

For example, our Chart component consists of three sub-components: LineChart, BoxplotChart and ScatterplotChart. They use D3 in the componentDidMount() and willReceiveProps() functions and that works great. Taking it to the extreme however, we could wrap everything in D3 as a React component, but who are we kidding then?

Use (the new) React developer tools

Image

The old React developer tools were already great but had some downsides: not automatically reflecting state and prop values (you had to reselect the node manually). The new version fixes this and makes inspecting and debugging so easy you can’t work without it once you’ve tried them.

You can see your component tree, track the flow of props throughout the application and inspect state.

Also, when you inspect a component using the regular Elements tab in Chrome Developer Tools, and then switch to the React tab, the component will get selected there too. Even better, you can now use $r as a reference to the component in the console!

The new version is also available for Firefox.

Take Webpack’s advice

Don’t blindly copy require() statements from file to file like I did, as this will grow your bundle size and will take a lot of time cleaning up.

Running $ webpack -cp will probably give lots of warnings which you can usually ignore. But to get the best filesizes resulting in the best possible mobile performance, it’s good practice to at least clean up your own code.

Webpack will tell you which variables are not used and which require() statements are not needed.

Also, use Webpack’s xhr bundle loading feature in conjunction with react-router, or go isomorphic for larger multipage apps. This will make the app load faster and also cuts down on Webpack’s hot-reloading time during development.

Example:

...
var TableAppPage = null;
var TableAppLoader = React.createClass({
  componentDidMount: function() {
    var self = this;
    require.ensure(['./components/TableApp/TableApp'], function() {
      TableAppPage = require('./components/TableApp/TableApp');
      self.setState({});
    });
  },
  render: function() {
    if(TableAppPage) {
      return <TableAppPage {...this.props} />;
    } else {
      return <div>Pagina wordt geladen...<Spinner style= spinnerName='cube-grid' noFadeIn /></div>;
    }
  }
});
...
var routes = (
  <Route name="app" path="/" handler={App}>
    <Route name="table" handler={TableAppLoader}/>
    <Route name="map" handler={MapAppLoader}/>
    <Route name="chart" handler={ChartAppLoader}/>
    <DefaultRoute handler={TableAppLoader}/>
  </Route>
);

Router.run(routes, function (Handler) {
  React.render(<Handler />, document.getElementById('efcis-app'));
});

Future

For future projects, I’ll definitely consider using React.js again. But I’ll try to incorporate the following tools and practices to make it even better: