react-router-template

3.2.0 • Public • Published

react-router-template

NPM version Build Status Dependency Status

Quickly render universal react-router components using a simple API.

// Server
import template from 'react-router-template';

const render = template({
	routes,
	wrapHtml: html => `<!DOCTYPE HTML>${html}`,
	wrapComponent: component => (
		<Provider store={store}>
			{component}
		</Provider>
	)
});

app.get('*', (req, res) => {
	render(req).then(html => res.end(html));
});
// Browser
import template from 'react-router-template';

const render = template({
	routes,
	wrapComponent: component => (
		<Provider store={store}>
			{component}
		</Provider>
	)
});

render(document.getElementById('container'))
	.then(() => console.log('rendering complete'));

overview

react-router-template is a universal solution to rendering a react-router hierarchy on both the server and the browser.

Simply provide a template on how to render components with the option to alter the output as you like, then use the returned render function to either output a string (on the server) or mount on the DOM (in the browser). Common alterations to the output are wrapping it in a <Provider> or prepending a <!DOCTYPE>.

  • ignore complexities of universal rendering
  • use async routes on the browser with async rendering
  • juggle the output in any way you need.

Server usage

Server-side templates are defined with the following options:

  • routes - the <Route> hierarchy required for react-router to match against and render. Supports async routes
  • createElement - optional function passed to the <RouterContext>
  • wrapComponent - optional function that is passed the resulting <RouterContext>, allowing you to wrap / compose it in Providers (e.g. react-redux's <Provider>) or other additional React components
  • wrapHtml - optional function that allows you to decorate the resultant HTML string

Both wrapComponent and wrapHtml are evaluated as Promises, and thus can be either synchronous or asynchronous.

import template from 'react-router-template';
import { Route } from 'react-router';
import { Provider } from 'react-redux';
import { App, Index } from './components'; // or wherever you place them

const routes = (
	<Route path="/" component={App}>
		<Route path="/index" component={Index} />
	</Route>
);

// most basic use case
const render = template({ routes });

// using more options
const anotherRender = template({
	routes,
	wrapHtml: html => `<!DOCTYPE HTML>${html}`,
	wrapComponent: component => (
		<Provider store={store}>
			{component}
		</Provider>
	)
});

The output is a render() function that returns a Promise resolving into a string:

// assuming an express route
app.get('*', (req, res) => {
	render(req).then(html => res.end(html));
});

In the example above, the req object is passed in directly, but:

  • any object with the originalUrl property is sufficient.
  • a string representing the path to render is also sufficient.
// both of these will work
render('/some-path');
render({ originalUrl: '/some-path'});

not founds and redirects

When the path specified cannot be found or redirects, the render function will throw an Error with the following properties:

  • .status or statusCode will be a number, 404 for not found, 302 for redirects.
  • For redirects, .location or .url will also be present to allow the developer to take the appropriate action
app.get('*', (req, res) => {
	render(req)
		.then(html => res.end(html))
		.catch(err => {
			const { status, location } = err;
			if (status === 404) {
				// handle not found
			} else if (status === 302) {
				res.redirect(location);
			} else {
				// other error handling
			}
		});
});

Browser usage

Browser templates are defined with the following options:

  • routes - the <Route> hierarchy required for react-router to match against and render. Supports async routes
  • createElement - optional function passed to the <RouterContext>
  • wrapComponent - optional function that is passed the resulting <RouterContext>, allowing you to wrap / compose it in Providers (e.g. react-redux's <Provider>) or other additional React components

wrapComponent is evaluated as a Promise and thus can be either synchronous or asynchronous.

import template from 'react-router-template';
import { Route } from 'react-router';
import { Provider } from 'react-redux';
import { App, Index } from './components'; // or wherever you place them

// most basic use case
const render = template({
	routes
});

// using more options
const render = template({
	routes,
	wrapComponent: component => (
		<Provider store={store}>
			{component}
		</Provider>
	)
});

The resulting render() is a promise-returning function that takes up to 2 arguments, rather than just one. Instead of a path (or an object containing originalUrl):

  • the first argument can be a history object (or object containing a history property).
  • the second argument, if present, should be the DOM element to mount the React component to.
// some client JS entry point
import { browserHistory } from 'react-router';

const target = document.getElementById('container');

render(browserHistory, target)
	.then(() => console.log('rendering complete'));

If the history object is not specified (i.e. only one argument, the target, is supplied), then it is assumed that react-router's browserHistory will be used.

// both of these will work
render({ history: browserHistory}, target);
render(target);

This reflects the nature of client-side rendering - the path is usually determined from the browser context rather than being supplied, and the second argument matches React's own render signature.

NOTE: There is no wrapHtml option, because on the browser React is primarily concerned with mounting the component onto an existing DOM, not producing any HTML string output.

error handling

There is no special API for errors on the browser, apart from the fact that any error is caught as a Promise rejection.

Redirects are handled automatically on the browser for you via the supplied history object.

Any errors created are no different than if you weren't using react-router-template, but just React's native .render() method.

render(browserHistory, target)
	.catch(err => console.error(err));

note regarding client-side bundlers like webpack

Bundlers are expected to use the entrypoint specified by browser in this module's package.json as per the specifications outlined here

Package Sidebar

Install

npm i react-router-template

Weekly Downloads

1

Version

3.2.0

License

ISC

Last publish

Collaborators

  • masotime