In context of a single page application (SPA) routing is a feature that allows you to show different content depending on the URL, but without reloading the page. To the user it looks like navigating different pages.
Routing can use either modern HTML5 History API to change URL completely or use hash URLs, storing route in the URL part after hash mark (#).
To facilitate routing Frint comes with with two packages: frint-router and frint-router-react.
frint-router provides you with router services responsible for storing URL, notifying of URL
changes and matching it against different path. The services differ in how and where they store URL,
but provide identical API so you can freely replace them with each other. This package isn't bound
to any view library.
frint-router-react is a React-specific package which complements frint-router and makes routing
really easy if you chose React as your view library by providing some handy components.
You will need both frint-router and frint-router-react for this tutorial as well as frint,
react and frint-react.
$ npm install --save frint-router frint-router-react frint react frint-react
Let's start by creating components. We will have two components for content pages: HomePage and
AboutPage. We are also going to have RootComponent which will serve as
container for navigation links and content pages.
We will need to import React for creating components as well as Link and Route components
from frint-router-react.
import React from 'react';
import { Link, Route } from 'frint-router-react';
const HomePage = () => <div>Home page</div>;
const AboutPage = () => <div>About page</div>;
const RootComponent = () => {
return (
<div>
<div className="links">
<Link to="/" activeClassName="is-active" exact>[Home]</Link>
<Link to="/about" activeClassName="is-active">[About]</Link>
</div>
<div className="content">
<Route path="/" component={HomePage} exact />
<Route path="/about" component={AboutPage} />
</div>
</div>
);
};
Let's go over what happens here.
RootComponent renders section with navigation links using Link component. Link component
creates an anchor or a button leading to a certain URL. It also applies CSS class is-active
(passed as activeClassName prop) to the link element automatically when current URL matches the
one passed to the Link.Route component to switch between content pages. Whenever path passed to
Route component matches current URL it will render component from the props. Just as with
Link you can choose whether it should be an exact match. You can also pass a Frint app as an app
prop instead of component.There are three router services available:
BrowserRouterService: uses modern HTML5 History APIHashRouterService: for legacy browsersMemoryRouterService: useful for testsFor the purpose of this tutorial we will use BrowserRouterService:
import BrowserRouterService from 'frint-router/BrowserRouterService';
Now let's create an app that makes use of the RootComponent and router we've just created. We
pass them as providers to the app. Names of the providers are defined here by convention.
frint-react requires that app's main component would be a provider named component.frint-router-react relies on router provider name for Link and Route to get and set
current URL. If you want to use Link or Route in your child apps you'll need to cascade the
router provider to them by adding cascade: trueimport { createApp } from 'frint';
import BrowserRouterService from 'frint-router/BrowserRouterService';
const RouterApp = createApp({
name: 'RouterApp',
providers: [
{
name: 'component',
useValue: RootComponent
},
{
name: 'router',
useFactory: function() {
return new BrowserRouterService();
},
cascade: true,
},
],
});
We have everything ready to start the app. We will need frint-react to render our app into HTML
element (with id root in this case).
import { render } from 'frint-react';
window.app = new RouterApp();
render(
window.app,
document.getElementById('root')
);
Now if you open it in a browser you should see a page with [Home] and [About] links and 'Home page' content. If you click the links content will change accordingly.
Another useful feature of frint-router-react is default route which allows you to render a
component or an app when no other route matched. To enable this behaviour add a Route without a
path to your group of Route components and them wrap them into Switch.
import { Route, Link, Switch } from 'frint-router-react';
const NotFoundPage = () => <div>Not found</div>;
const RootComponent = () => {
return (
<div>
<div className="content">
<Switch>
<Route path="/" component={HomePage} exact />
<Route path="/about" component={AboutPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
</div>
);
};
Now whenever you navigate to URL that doesn't match / or /about you'll see NotFoundPage
rendered.
Note: in Switch only the first matching Route gets rendered.
It is also possible for child components to define their own subroutes. So for example if you want
AboutPage to have subpages you can render Link and Route to it.
const AboutUs = () => <article>Some content about us...</article>;
const AboutThem = () => <article>Some content about them...</article>;
const AboutPage = ({ match }) => {
return (
<div>
<h2>About page</h2>
<ul>
<li><Link to={`${match.url}`}>About</Link></li>
<li><Link to={`${match.url}/us`}>About us</Link></li>
<li><Link to={`${match.url}/them`}>About them</Link></li>
</ul>
<Switch>
<Route path={`${match.url}/us`} component={AboutUs} />
<Route path={`${match.url}/them`} component={AboutThem} />
</Switch>
</div>
);
};
Because AboutPage is rendered using Route component it receives a match object as a prop. This
object contains url which let's you define routes relative to the current.
It is important that the parent Route doesn't have exact prop so that it would match all the URLs
starting with the path.
In your path you can also define params which will be passed to your component once the path is
matched. Imagine we have a photo gallery where we show a list of photo names and by clicking on the
name you can see the name and the photo itself. Below you can find how it can be implemented by
using route params. PhotosPage acts as a container with routing for two nested components: Photos
and Photo.
const Photos = ({ match }) => {
return (
<div>
<h1>Photos</h1>
<ul>
<li><Link to={`${match.url}/1`}>Photo #1</Link></li>
<li><Link to={`${match.url}/2`}>Photo #2</Link></li>
<li><Link to={`${match.url}/3`}>Photo #3</Link></li>
</ul>
</div>
);
};
const Photo = ({ match }) => {
return (
<div>
<h1>Photo #{match.params.photoId}</h1>
<div>
<img src={`/static/img/photos/${match.params.photoId}`} />
</div>
</div>
);
}
const PhotosPage = ({ match }) => {
return (
<Switch>
<Route path={`${match.url}/:photoId`} component={Photo} />
<Route component={Photos} />
</Switch>
);
};
To learn more about usage of Frint routing you can take a look at another example from our monorepo.
Install frint-cli package, then init the router example and run it.
$ npm install -g frint-cli
$ mkdir my-frint-app
$ cd my-frint-app
$ frint init --example router
$ npm install
$ npm start
It will launch the example in your browser for you, after taking care of bundling.