Many React applications use react-router for routing. However, what if you do not need the large featureset it provides, and want something simpler and smaller? In this tutorial, I will show you two ways to use kingfish for routing in a React application or website.

To understand this tutorial, you should have a basic knowledge of React, kingfish, Express, and isomorphic rendering.

First method: React everywhere

If you are writing an application or a highly dynamic website, you probably want to use React to render every page. We will use kingfish to handle link clicks and URL changes, but will quickly move into React for everything else.

First, we need to set up our kingfish router, with a single catch-all route. I'm going to assume you already have an HTML file set up with an #app element to render into.

'use strict';

var kingfish = require('kingfish');
var React = require('react');
var ReactDOM = require('react-dom');

var App = require('./App');

var routes = {
    '/*': class {
        constructor() {
            this.name = 'app';
        }
        mount() {
            this.setParams(this.params);
        }
        setParams(newParams) {
            this.params = newParams;
            ReactDOM.render(<App url={this.params[0]} />, this.element);
        }
    }
};
var appElement = document.getElementById('app');

var router = new kingfish(routes, appElement);
router.start();

Now we need our App element, which is our top-level React component. We will define our pages as a simple array.

'use strict';

var React = require('react');

var pages = [{
    url: '',
    title: 'Home',
    handler: require('./routes/Home')
}, {
    url: 'about',
    title: 'About',
    handler: require('./routes/About')
}];

var App = React.createClass({
    render() {
        var url = this.props.url.split('/');
        var topLevelRoute = url[0];
        url.shift();
        var RouteHandler;
        var page = pages.find(page => {
            return page.url === topLevelRoute;
        });
        if (!page) {
            page = {
                url: topLevelRoute,
                title: 'Not Found',
                handler: require('./routes/404')
            };
        }
        RouteHandler = page.handler;

        var menuLinks = pages.map((page, i) => {
            var className = '';
            className += (page.url === topLevelRoute ? ' current' : '');
            return <a href={'/'+page.url} key={i} className={className}>{page.title}</a>;
        });

        return (
            <div>
                <header>
                    <h1>Routing for a React application using kingfish: Method 1</h1>
                    <div>
                        {menuLinks}
                    </div>
                </header>
                <div className="page"><RouterHandler url={url} /></div>
            </div>
        )
    }
})

We have all the routes stored in the routes folder, like you might do in a simpler react-router application. These are just regular React components.

We set up our kingfish route to give us the URL, which we split into an array. We get the first URL segment to determine which route handler to use, remove it from the array, and give the rest of the array to the route handler. This lets the handler have its own list of child routes it can render, accept a query string, or anything else you need.

In this example we also build the navigation menu from the array, but you could hard-code it if you choose as well. This menu is also regular hyperlinks--kingfish will handle them like usual even though they are rendered by React. You do not have to do anything to make this happen.

Second method: Static pages using template strings, dynamic pages using React

If you are writing a website where some pages are dynamic, but a lot are mostly static, you may not want to use React on every page. If you only load React on the pages that need it, you can potentially save quite a few kilobytes. This is where kingfish starts to really shine.

For this method we will use kingfish's routing features to handle every route. We will have a mostly static page, and a dynamic page rendered using React.

Here is our modified top-level Kingfish router. There is no longer a React App element.

'use strict';

var kingfish = require('kingfish');

var topLevelRoutes = {
    '/reactpage': require('./reactpage'),
    '/': require('./routes/staticpage')
};

var routes = require('./routes');
var appElement = document.getElementById('app');

var router = new kingfish(routes, appElement);
router.start();

The top-level route is now in a separate file. This is because we will be using this on the server in this example, for isomorphic rendering.

'use strict';

var kingfish = require('kingfish');

module.exports = {
    '/*': class {
        constructor() {
            this.name = 'app';
        }
        mount() {
            this.childElement = this.element.getElementsByClassName('page')[0];
            this.childRouter = new kingfish(topLevelRoutes, this.childElement);
            this.setParams(this.params);
        }
        setParams(newParams) {
            this.params = newParams;
            this.childRouter.render('/'+this.params[0]);
        }
        renderString() {
            var router = new kingfish(topLevelRoutes);
            var topLevelRouteName = this.params[0].split('/')[0];
            return router.renderString('/'+this.params[0]).then((childHtml) => {
                return `
<div>
    <header>
        <h1>Routing for a React application using kingfish: Method 2</h1>
        <div>
            <a href="/">Static Page</a>
            <a href="/reactpage">React Page</a>
        </div>
    </header>
    <div class="page">${childHtml}</div>
</div>
                `;
            });
        }
    }
};

You may notice we never actually render the application, only the contents of each route. In fact, we try to get the page element without rendering it. This is because we will do this on the server--kingfish has full support for isomorphic applications. You can easily render this application on the server with Express.


var express = require('express');
var app = express();
var Kingfish = require('kingfish');

// You can render your HTML using anything you like,
// such as Handlebars or just a template string.
var renderLayout = require('./renderLayout');

app.all('*', function(req, res, next) {
    var routes = require('./routes');

    var router = new Kingfish(routes);
    router.renderString(req.originalUrl).then(function(content) {
        var pageContent = renderLayout(content);
        res.send(pageContent);
    }).catch(function (error) {
        res.status(500).send(error.message);
    });
});

app.listen(3000);

Now we need the actual routes. Here is the static route.

'use strict';

module.exports = class {
    constructor() {
        this.name = 'staticpage';
    }
    mount() {
        this.element.innerHTML = this.renderString();
    }
    setParams(newParams) {
        this.params = newParams;
        this.element.innerHTML = this.renderString();
    }
    renderString() {
        return `
<h2>Static page</h2>
<p>The contents of this page are mostly static, and thus using a large view layer such as React is unnecessary.</p>
<p>Some simple dynamic content is still easy to achieve.</p>
        `;
    }
};

And finally here is the dynamic route, using React for rendering.

'use strict';

var React = require('react');
var ReactDOM = require('react-dom');

var DynamicPage = React.createClass({
    render() {
        return (
            <div>
                <h2>Dynamic Page</h2>
                <p>The contents of this page are rendered using React, and thus can easily be highly dynamic.</p>
            </div>
        );
    }
})

module.exports = class {
    constructor() {
        this.name = 'dynamicpage';
    }
    mount() {
        this.setParams(this.params);
    }
    setParams(newParams) {
        this.params = newParams;
        ReactDOM.render(<Dynamicpage />, this.element);
    }
    unmount() {
        ReactDOM.unmountComponentAtNode(this.element);
    }
    renderString() {
        // You could use ReactDOMServer to render a string here
        return 'Loading...';
    }
};

Redirects

What if you want to redirect the user to another page in the browser? You can do this easily by creating a helper function.

function redirect(newUrl) {
    history.pushState(undefined, '', newUrl);
    router.render(newUrl);
};

Then just call this function to redirect, like this: redirect('/newurl').

Conclusion

In this tutorial I have shown, using code examples, how to use kingfish to route a website or application, while using either template strings or React for rendering.

To get the full benefit of this, you may want to use code splitting with Webpack or externals with Browserify so that React isn't even downloaded unless it is needed for the current page.