Skip to content

Latest commit

 

History

History
166 lines (133 loc) · 5.76 KB

routing-and-navigation.md

File metadata and controls

166 lines (133 loc) · 5.76 KB

Routing and Navigation

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 (see matchURI() method in client/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 in client/router.js)
  • History — client-side navigation library powered by history npm module (the same one used in react-router) that helps with transitioning between pages (screens) in the browser without causing full-page refresh (see client/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) or System.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>.

Related Articles