ASP.NET Core Starter Kit uses a custom minimalistic (under 100 LOC) declarative routing approach that is easy to customize. It's comprised of five major parts:
- Routes — the list of application routes in JSON format (see
client/routes.json
) - Routes Loader — a custom loader for Webpack that converts routes from JSON to JavaScript on
build (see
client/utils/routes-loader.js
) - URL Matcher — a function that checks if a given URI matches to the route's
path
string (seematchURI()
method inclient/router.js
) - Route Resolver — a function just resolves a URI string to the first matched route, fetches
all the required data and returns a React component to render (see
resolve()
method inclient/router.js
) - History — client-side navigation library powered by
history
npm module (the same one used inreact-router
) that helps with transitioning between pages (screens) in the browser without causing full-page refresh (seeclient/history.js
)
The list of routes is just an array where each item contains a path
- parametrized URL path string
and a view
field that points to a corresponding UI (page or screen) component within the project's
file structure. For a simple to-do app, this list of routes may look like this (routes.json
):
[
{
"path": "/",
"view": "./views/home"
},
{
"path": "/tasks/:status(pending|completed)?",
"view": "./views/tasks/list"
},
{
"path": "/tasks/new",
"view": "./views/tasks/new"
},
{
"path": "/tasks/:id",
"view": "./views/tasks/details"
}
]
This list of routes is referenced inside the main application file (where the React app is beeing
bootstrapped) by using routes-loader
(see
client/main.js
):
import routes from '!!./utils/routes-loader!./routes.json';
If you're new to Webpack's "loader" concept, please refer to https://webpack.github.io/docs/loaders
The routes-loader
performs three tasks:
- Converts JSON-based routes into JavaScript
- Converts parametrized URL path strings into regular expressions by using
path-to-regexp
- Wraps page/screen UI components' path strings into either
require.ensure(..)
(Webpack 1.x) orSystem.import(..)
(Webpack 2.x). For more information see code-splitting in Webpack docs.
For example, a route like this:
{
"path": "/tasks/:id",
"view": "./views/tasks/details"
}
Will become:
{
path: '/tasks/:id',
pattern: /^\/tasks\/((?:[^\/]+?))(?:\/(?=$))?$/i,
keys: [{ name: 'id', pattern: '[^\\/]+?', ... }],
view: './views/tasks/details',
load: function() { return System.import('./views/tasks/details'); }
}
Given the list of routes you can ask the router to "resolve" the given URI string to a React component. The code for that may look something like this:
router.resolve(routes, { pathname: '/tasks/123' }).then(component => {
ReactDOM.render(component, container);
});
The resolve(routes, context)
method will find the first route from the list matching to the
/tasks/123
URI string, execute its load()
method, and return corresponding React component as a
result wrapped into ES6 Promise (see core/router.js
.
If a route contains some REST API or GraphQL endpoints as data requirements for the given route,
the resolve(..)
method can also fetch the required data from these endpoints. For example, a
route that needs to fetch a task by its ID may look like this:
{
"path": "/tasks/:id",
"view": "./views/tasks/details",
"fetch": {
"task": "GET /api/tasks/$id",
}
}
Finally, you can hook the router's resolve(..)
method to be called each time when a user navigates
(transitions) between pages. The code for that may look something like this:
function render(location) {
router.resolve(routes, location)
.then(renderComponent)
.catch(error => router.resolve(routes, { ...location, error }).then(renderComponent));
}
history.listen(render);
render(history.getCurrentLocation());
For more information about how the history
npm module works please visit:
https://github.com/ReactJSTraining/history/tree/master/docs
All transitions between pages must be performed by using this module, for example:
import React from 'react';
import history from '../../core/history';
class HomePage extends React.Component {
transition = event => {
event.preventDefault();
history.push({ pathname: event.currentTarget.pathname });
};
render() {
return (
<div>
<h1>Home Page</h1>
<p><a href="/tasks/123" onClick={this.transition}>Show task #123</a></p>
</div>
);
}
}
The transition(event)
method above cancels default behavior of the <a>
element that causes
full-page refresh and instead redirects a user to the /tasks/123
page by using HTML5 History API.
This transition is then handled by history.listen(render)
listener inside the
main.js
file.
RSB comes with a helper component that can be used instead of <a>
elements, see
client/components/Link/Link.js
. So, instead of writing
<a href="/tasks/123" onClick={this.transition}>Show task #123</a>
you can have <Link to="/tasks/123">Show task #123</Link>
.
- You might not need React Router by Konstantin Tarkus