The React Fact that will Change your Mindset

THE FACT: ReactDOM.render is not destructive

If you repeatedly call ReactDOM.render on a component, passing in different props each time, then React doesn’t tear down that component and start from scratch. React updates the component’s props while maintaining its internal state. Before showing how this fact will change your mindset, let’s demonstrate its veracity with the aid of an example.

The example shows a Timer component that increments every second. The colour of the number displayed alternates between red and blue every other second. The Timer stores the seconds count in state and the display colour in props. To toggle the colour, we run a setTimeout every two seconds that calls ReactDOM.render passing the new colour prop to the Timer. You can see that ReactDOM.render is not destructive because the count, held in state, doesn’t reset when we change the colour:

You Don’t Need the React Router

Let’s modify the Timer example so that, instead of changing the display colour inside of a setTimeout, we pass the colour in the URL. We’ll add ‘red’ and ‘blue’ Hyperlinks that set the respective colour in the URL. Whenever the hash changes, we’ll call ReactDOM.render on the Timer component passing in the colour prop:

window.addEventListener('hashchange', function(){
  ReactDOM.render(
    <Timer colour={location.hash} />,
    document.getElementById('container')
  );
};

Without even trying to, we’ve created a basic router. We’ve been led to believe that updating components when the URL changes is so difficult that only the React Router can do it. Think of the complexity the React Router would introduce to the Timer example. By owning the Timer component, the React Router would prevent us from passing in the colour prop. Realising that ReactDOM.render isn’t destructive is like casting a magic spell that makes the React Router disappear. Taking back ownership of your components will change your mindset.

Giving up the React Router doesn’t mean giving up the ability to share UI across pages because React supports templates. A React template is any component that hosts a placeholder DOM element. We render this template when our app loads and target all subsequent ReactDOM.render calls at the template’s placeholder. Below is an example of an App template with a ‘content’ placeholder:

var App = () => (
  <div>
    <h1>Example<h1>
    <div id="content"></div>
  <div>
);

ReactDOM.render(
  <App />,
  document.getElementById('container')
);

If we add this App template to the Timer example then, whenever the URL changes, we ReactDOM.render the Timer component into the ‘content’ placeholder of the template instead of the ‘container’ element in the HTML:

ReactDOM.render(
  <Timer colour={location.hash} />,
  document.getElementById('content')
);

You’ve nothing to fear on the performance front because rendering your own templates and components is at least as fast the React Router doing it for us:

Try the Navigation Router

I’m not suggesting you don’t need a router. Just that you use one that embraces, rather than competes with, React. One that doesn’t render your components and that focuses instead on what React doesn’t do. I recommend my Navigation router. Its data-first approach to routing complements React’s top-down render as you can see in the Timer example below:

Streamlined React Components

A React component’s render should be a pure function of its props. Pass in the same props and you should always get back the same results. You can then streamline the component with a shouldComponentUpdate function that bypasses rendering when the props haven’t changed. This optimisation technique works smoothly, except when it comes to Hyperlinks. Hyperlinks have a nasty habit of forcing props into components, making them update when they shouldn’t have to. In this post, I’ll show how to tame your Hyperlinks using a new declarative approach to routing.

Let’s start with an example of a list of items that can be sorted and filtered. It’s made up of three components: Sort, Filter and List. If we use Buttons, rather than Hyperlinks, to implement the sorting and filtering functionality, then here’s the props each component needs:

  • Sort component needs the sort prop to determine the new sort order.
  • Filter component needs the filter prop to style the currently active filter.
  • List component needs the sort, filter and items props to display the list

Components coloured by props when Buttons used

If we imagine that the filter prop is blue and the sort prop yellow, we can draw a picture of the example and colour in the components to match their props. The Sort component is yellow because it needs the sort prop, the Filter’s blue to match the filter prop and the List is green because it expects both yellow and blue props.

Any component that isn’t green can be streamlined with a shouldComponentUpdate function. The green List component can’t be streamlined because it has to update when either the filter or the sort changes. The yellow Sort component, on the other hand, only has to update when the sort changes:

shouldComponentUpdate(oldProps, props) {
  return oldProps.sort !== props.sort;
}

However, all the components turn green if we implement the sorting and filtering using Hyperlinks instead of Buttons. The key difference between a Hyperlink and a Button is that a Hyperlink has an href attribute. To populate an href we need both the filter and the sort. For example, the Sort component needs the filter prop as well as the sort prop so it can build a URL that holds the current filter along with the new sort order.
Components coloured by props when Hyperlinks used
You can see how Hyperlinks force props into your components. They force the filter prop into the Sort component and the sort prop into the Filter component. These extra props turn the Sort and Filter components green. Green components can’t be streamlined with shouldComponentUpdate functions because they always have to update. But, we can turn the Sort back to yellow and the Filter back to blue by using a declarative approach to routing.

Declarative Routing

The Navigation router introduces a declarative way to build Hyperlinks. We’ll use it to remove the filter prop from the Sort component and the sort prop from the Filter component, turning them back to yellow and blue respectively. First, let’s see what the sorting Hyperlink looks like built using a traditional imperative router like the React Router:

var data = {
  sort: this.props.sort === 'up' ? 'down' : 'up',
  filter: this.props.filter
};
<Link to={`${data.filter}/${data.sort}`} />

You can see that the React Router expects us to pass the URL to the Link component. With this imperative approach, there’s no way to turn the Sort component yellow because we can’t build the URL without the filter prop. Compare this to the equivalent sorting Hyperlink built using the RefreshLink component from the Navigation router:

<RefreshLink
  navigationData={{
    sort: this.props.sort === 'up' ? 'down' : 'up',
    filter: this.props.filter
  }} />

This code is much more declarative. Instead of building the URL ourselves, we’re telling the RefreshLink what data we want in the href and letting it build the URL for us. But, the Sort component is still green. To remove the filter prop and turn the Sort component yellow, we can get the RefreshLink component to keep track of the current data rather than pass the filter in:

<RefreshLink
  navigationData={{sort: this.props.sort === 'up' ? 'down' : 'up'}}
  includeCurrentData={true} />

This code is even more declarative. We specify that the href should include the current filter value without deciding how and when to get it. Without the filter prop, the yellow Sort component can be streamlined with a shouldComponentUpdate function that prevents it from updating unless the sort prop changes. This doesn’t stop the latest filter making it into the href because the RefreshLink auto updates whenever the current data changes.

We can apply these declarative techniques to the filter Hyperlinks to turn the Filter component back to blue. If we also declaratively style the active filter Hyperlink, then we can turn the Filter component white so that it never has to update. You can see the yellow Sort and white Filter components in action in this JsFiddle of the complete example.

A Route by any Other Name

If your router doesn’t support named routes then you’re missing out. Without named routes, changes to URLs ripple throughout your code base. Named routes insulate your code from URL changes, allowing you to safely delay decisions about how your URLs should look.

When the React Router brought in async configuration it dropped support for named routes. That so few people complained about their removal I attribute to that router’s flawed implementation of them. To see how good named routes can be I’ll show you how they work in my Navigation router.

Imagine we’re tasked with displaying a paged list of items. Our Navigation router configuration will start out looking something like:

var stateNavigator = new StateNavigator([
  {key: 'list', route: 'list'}
]);

We can then use the NavigationLink component to build a Hyperlink that navigates to the second page of items as shown below. You can see that, because we’re using named routes, we don’t hard-code the URL. Instead we pass the key from the configuration together with the page number and leave it up to the router to generate the URL for us:

<NavigationLink stateKey="list" navigationData={{page: 2}} />

This generates a URL of ‘/list?page=2’. We’re happy to live with this scruffy URL throughout development but it needs smartening up before we go live.

We’ll make the URL read ‘/items/2’ instead of ‘/list?page=2’. With the React Router’s (now defunct) named routes support, we couldn’t change page number from a query string to a route parameter without corresponding code changes. But with the Navigation router we can, because our application code isn’t aware of where in the URL the page number lives:

var stateNavigator = new StateNavigator([
  {key: 'list', route: 'items/{page}'}
]);

The icing on the cake is for the URL back to the first page to be ‘/items’ instead of ‘/items/1’. With any other router this would require a code change. With the Navigation router this is only a configuration change, because we can specify a default value for the page number:

var stateNavigator = new StateNavigator([
  {key: 'list', route: 'items/{page?}', defaults: {page: 1}}
]);

The Real React Router Use Cases?

I’m a big fan of Dan Abramov so I tweeted him my blog about how you’re better off using my Navigation router than the React Router. He wasn’t convinced and tweeted back that I was ignoring the real React Router use cases like server rendering and code splitting. This surprised me because I think of server rendering as a React use case and code splitting as a use case for bundlers like webpack. In fact, the Navigation router doesn’t have to do anything special to get them to work, as you can see from these server rendering and code splitting examples. I’ll explain how the React Router, on the other hand, actually breaks server rendering and code splitting.

The Real React Router Use Cases

Server Rendering

With server rendering, users no longer have to wait until the JavaScript downloads and executes before they see any content. React lets you render your app on the server so you can return meaningful HTML in the first response. Along with the HTML, you also send back the JSON props you used to render the component. Once the JavaScript loads, you use these props to render that same component to the DOM, allowing the client to catch up with the server rendered content.

You can see from my server rendering example that this approach plays nicely with the Navigation router. However, the same can’t be said for the React Router. That’s because the React Router is responsible for rendering your components, preventing you from passing props into them. If you can’t pass props into your components then server rendering breaks. Although you can get it working again by bringing in Redux, you shouldn’t let your router dictate your architecture.

Code Splitting

Code splitting allows you to divide up your JavaScript into separate bundles and load them on demand. This keeps your application feeling snappy, avoiding slow page load times when users first visit your site. You can individually bundle up your components and load each one just before the corresponding route becomes active.

You can see this approach in action in my code splitting example, where I put the bundle loading logic inside the Navigation router’s transition hooks. However, because the React Router owns your components, you don’t have this luxury. You have to place the code splitting logic inside the configuration.

<Route path="courses/:courseId" getComponent={(location, cb) => {
  // Load the component bundle
  cb(null, Course)
}} />

To see why this difference is important let’s consider a typical example where you must fetch some data before rendering the new component. There are now two asynchronous calls: the AJAX data request and the component bundle. With the Navigation router, you can execute these requests in parallel from inside the transition hook. But with the React Router, you have to suffer the performance cost of running them in serial because you can’t put the AJAX request inside the getComponent function.

TL;DR

I’m surprised that Dan Abramov wasn’t convinced by my previous blog that shows you’re better off using my Navigation router than the React Router. I’m sure my server rendering and code splitting examples will persuade him this time.

React Routing Debunked

THE MYTH: You can’t build a React web app without the React Router

The Reality

The React Router maintainers argue that your code will be a mess of branching logic (‘if’ conditions and ‘switch’ statements) if you don’t use their router. They construct an email application example with nested views to support their claim. But, it isn’t true. You can use any router with React without introducing branching logic. To show this, I’ve built the email example using my Navigation router:

The React Router is a templating library that associates components with routes. The top level route holds the template component which hosts the child route components. But the React Router is trying to solve a problem that doesn’t exist, because React supports templates natively. Here’s an example of a React template, written as a functional stateless component:

var App = () => <div id="content"></div>;

You don’t need the React Router to ensure the correct component is loaded when a route is hit. All routers notify you when the route changes. To load the right component without introducing branching logic, you add separate listeners to the individual route change events. In the email example, when the ‘inbox’ route is hit, you load the Inbox component into the ‘content’ placeholder in the App template:

ReactDOM.render(<App/>, document.body);
ReactDOM.render(<Inbox/>, document.getElementById('content'));

If you use the React Router, then you can’t pass props into your components. With any other router you can, because it’s up to you, and not the router, to create your components. The example’s Inbox component needs to display a list of messages. The React Router has no choice but to hold the list in the component’s internal state. But, by using another router, you can follow the React way and pass it in as props instead:

<Inbox messages={messages} />

I’ve debunked the myth that you can’t build a React app without the React Router. You don’t have to worry that your code will turn into a branching mess without it. You’ve seen that any other router gives you back control over your components. So, you can ditch state in favour of props. The components in my email application example are all functional and stateless. The ones from the React Router documentation aren’t.

Progressive Enhancement Enhanced

Every developer who listens to their heart will agree that progressive enhancement is the right way to build web apps. It’s the only development approach that embraces the idea that web content should be accessible to all users, regardless of browser or network. So why isn’t Progressive Enhancement, or PE, more popular?

For PE to be a viable approach, it must produce an app that’s easy to enhance. Because, without the enhancements, the app will be sluggish for all user interactions other than the initial page load. But traditional PE, by starting with the server rendered version, produces an app that’s too hard to enhance. With no client side abstraction to fall back on, we’re forced into writing messy DOM manipulation code.

With React, there’s a new way to do PE. We start with a basic client rendered version and then use React’s isomorphic capabilities to render this same app on the server. The end result is an app that’s easy to enhance because we have a React Component hierarchy on the client to interact with instead of the DOM.

All the examples I’ve seen use Node for the web server. To redress the imbalance, I’ve built an example using ASP.NET. It’s a typical master/details scenario consisting of two views. The first displays a list of people and the second an individual person’s details. In keeping with the new approach to PE, let’s start with the client rendering.

Client Rendering

First come the React Components, one for each view. We’ll keep the rendering of our Components synchronous because it will simplify matters when we come to the server rendering. That means keeping the Components free of data access code. Here’s what our Details Component looks like, where the selected person is passed in fully populated:

var Details = React.createClass({
    render: function() {
        var person = this.props.person;
        return (
            <div>
                <h2>{person.Name}</h2>
                <div>Date of Birth</div>
                <div>{person.DateOfBirth}</div>
            </div>
        );
    }
});

With this new approach to PE, the role of the JavaScript router cannot be overstated. It will be the making or breaking of our app. For reasons that will become apparent as you read on, we’re using the Navigation router. Here’s the Navigation router configuration for the two routes in our example master/details app:

var stateNavigator = new Navigation.StateNavigator([
    {key: 'people', route: ''},
    {key: 'person', route: 'person/{id}'}
]);

The configuration is made up of States. There’s a ‘people’ State and a ‘person’ State, one for each view. Here’s how to build the Hyperlink that selects a person, where the stateKey refers to the key of the destination State and the navigationData holds the route data:

<NavigationLink 
    stateKey="person"
    navigationData={{id: person.Id}}
    stateNavigator={stateNavigator}>
    {person.Name}
</NavigationLink>

We want to display the Details Component when this Hyperlink is clicked. We’ll do this by attaching a navigated function to the ‘person’ State. The Navigation router will automatically call this function whenever the ‘person’ State is navigated to. Inside this function we’ll ask React to render the Details Component into the content placeholder in our HTML:

var states = stateNavigator.states;
states.person.navigated = function() {
    React.render(<Details />, 
        document.getElementById('content'));
}

For the render to work, we must pass the selected person’s details as props to the Component. We need an ASP.NET JSON Web Api method so we can retrieve these details over Ajax:

public class PersonController : ApiController
{
    public Person Get(int id)
    {
        return new PersonRepository()
                       .People.First(p => p.Id == id);
    }
}

By creating a JSON Web Api we’ve inadvertently created two sets of routes. One set is held on the client for the Navigation router and the other is held on the server for the Web Api. Let’s do away with the separate Web Api configuration by reusing our client routes on the server.

Route Reuse

Routes in the Web Api are used to map requests to Controllers. So, if we plan to get rid of them, we’re going to need to replace the default Controller lookup mechanism. Instead, our custom Controller selector will use the Navigation router to do the request to Controller lookup. The Navigation router lets us map Urls to States by passing the requested Url into the start function:

stateNavigator.start(url);
var state = stateNavigator.stateContext.state;

To simplify the mapping, we’ve twinned the names of the Controllers to the names of the States. For example, a Url of ‘person/2’ returns the ‘person’ State which maps to the PersonController.

To run the Navigation router on the server, our JavaScript must be written inside Node using npm. This doesn’t mean duplicating our JavaScript because we’ll use browserify and gulp to bundle it up for use on the client. We’ll call into Node from C# using Edge.js, a library that lets us script Node in process.

Edge.js accepts a string containing the Node code and the JavaScript function to execute. Data is received from C# via the function’s first parameter and passed back from Node via its second callback parameter. Here is the C# getContext Func that calls Node passing in a Url and returns the matching State key:

Func<object, Task<object>> getContext = Edge.Func(@"
    var Navigation = require('navigation');
    var App = require('App');
 
    var stateNavigator = App.createStateNavigator();
    return function (url, callback) {
        stateNavigator.start(url);
        callback(null, stateNavigator.stateContext.state.key);
    }
");

Calls to Edge.js are async. In the Web Api, Message Handlers are the place to make async calls. We’ll create a ContextHandler Message Handler and override its SendAsync method passing the requested Url into our getContext Func. We’ll store the key returned in the request’s Properties collection so it can be accessed downstream by our custom Controller selector:

protected async override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, 
    CancellationToken token)
{
    var url = request.RequestUri.PathAndQuery;
    var key = (string) await getContext(url);
    request.Properties["key"] = key;
    return await base.SendAsync(request, token);
}

We’ll build our Controller lookup mechanism by creating a class that implements IHttpControllerSelector. It’ll use the State key from the request Properties to return the associated Controller Type from the SelectController method:

public HttpControllerDescriptor SelectController(
    HttpRequestMessage request)
{
    return new HttpControllerDescriptor
    {
        Configuration = request.GetConfiguration(),
        ControllerType = Type.GetType(
            "NavigationEdgeApi.Controllers."
            + request.Properties["key"]
            + "Controller", true, true)
    };
}

In WebApiConfig, we’ll register our Message Handler and replace the default Controller selector with our new one:

config.MessageHandlers.Add(new ContextHandler());
config.Services.Replace(typeof(IHttpControllerSelector), 
    new ControllerSelector());

With our Controller selector in place, we’ll return to the client. Clicking the Hyperlink to select a person with id 2 from the list navigates to a Url of ‘person/2’. An Ajax call to our JSON Api passing this Url will return that person’s details because the Urls on the client and server now match.

We’ll place this Ajax request inside a navigating function attached to the ‘person’ State. The Navigation router will automatically call this function before the ‘person’ State is navigated to, giving us a chance to suspend the navigation until the Ajax call returns:

states.person.navigating = function(data, url, navigate) {
    getJSON(url, function(resp) {
        navigate({ person: resp });
    });
}

Our navigated function attached to the ‘person’ State is passed the JSON data in its second parameter. We’ll hand on the selected person as props to the Details Component:

states.person.navigated = function(data, asyncData) {
    React.render(<Details person={asynctData.person} />, 
        document.getElementById('content'));
}

We’ve got our Client rendering working with just a single set of routes. We don’t have a separate set of routes for the JSON Api. Not only does this make the code simpler, but it smooths the path for the server rendering.

Server Rendering

Imagine that, rather than left clicking the person selection Hyperlink, we right clicked and opened it in a new tab. The Url requested would still be ‘person/2’ but we must return the person’s details as HTML instead of JSON. Rather than cluttering up our Controllers with conditional logic, we’ll use content negotiation to return different responses based on the content type requested.

To avoid duplicating code, we’ll reuse our Details Component on the server to render the HTML. To help locate the Details Component based on the incoming request, we’ll add it to the ‘person’ State of our Navigation router configuration:

{key: 'person', route: 'person/{id}', component: Details}

Then we’ll use the Navigation router to look up the Component for a given Url, much like we did for our Controller lookup mechanism:

stateNavigator.start(url);
var component = stateNavigator.stateContext.state.component;

We’ll use Edge.js again to create a render Func that returns the HTML for a given Url. To perform the rendering for ‘person/2’, this Func needs the person JSON data so it can build the props to pass to the Details Component. Once the Component’s built, it can return the HTML via a call to React’s renderToString function:

Func<object, Task<object>> render = Edge.Func(@"
    var React = require('react');
    var Navigation = require('navigation');
    var App = require('App');
                 
    var stateNavigator = App.createStateNavigator();
    return function (data, callback) {
        stateNavigator.start(data.url);
        var props = {};
        props[stateNavigator.stateContext.state.key] = data.item;
        var component = React.createElement(
            stateNavigator.stateContext.state.component, props);
        callback(null, React.renderToString(component));
    }
");

We’ll create a RenderHandler Message Handler that intercepts the JSON response. If the request originated from an Ajax call then it lets the response continue unchanged. Otherwise, it passes the JSON into our render Func and overwrites the response with the HTML returned.

protected async override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, 
    CancellationToken token)
{
    var response = await base.SendAsync(request, token);
    if (request.Content.Headers.ContentType == null)
    {
        var html = (string) await render(new { 
            url = request.RequestUri.PathAndQuery, 
            item = ((ObjectContent) response.Content).Value
        });
        response.Content = new StringContent(
            "<div id='content'>" + html + "</div>");
        response.Content.Headers.ContentType 
            = new MediaTypeHeaderValue("text/html");
    }
    return response;
}

The last piece of this PE puzzle is to get the server and client rendering working together. After the HTML is returned we want React to take over on the client so that subsequent Hyperlink clicks result in Ajax requests. By sending the props along with the server rendered HTML, we can trigger a client render after the page loads. This allows React to catch up with the server rendered content.

TL;DR

With isomorphic React, there’s a new way to do Progressive Enhancement that blows the old way out of the water. I’ve built an example using ASP.NET that shows this approach is open to all developers, not just those using Node for their web server. There’s not an ounce of duplicated code. Even the routes and JSON Api are shared across the client and server rendering.

The JavaScript Router that’s got Baked in Breadcrumbs

Hansel is the fairytale boy who left a trail of breadcrumbs in his wake so that he and sister Gretel could find their way out after being abandoned in the woods. Very forward and backward thinking of him. If Hansel and Gretel were navigating a website rather than a woods, the breadcrumbs become a set of Hyperlinks signposting their journey. The Navigation router is the first JavaScript router to dynamically track a user’s path through a website, dropping Hyperlink breadcrumbs as they go.

With other routers, you might’ve resorted to manipulating browser history. But using history.go(-1) is like forcing Hansel and Gretel to walk backwards along the trail and, because browser history can’t be interrogated, might lead the unsuspecting travellers into an entirely different woods. If you manually tracked the path, instead, your Hyperlinks might disappear when the browser is refreshed, much as Hansel’s breadcrumbs disappeared when set upon by birds.

The Navigation router stores its breadcrumb trail in the Url, so it won’t be eaten when the page is refreshed or opened in a new tab. You can see the breadcrumb trail in action in Angular, Knockout or React. These examples display a list of people. Selecting a person takes you through to their details and drops a Hyperlink breadcrumb that returns you back to the list of people.