RiotJS Router
Tags: riotjs, beginners, javascript, frontend
Published: 2024-04-28 11:04:40

This article covers creating a Riot app coupled with [Riot-Route](https://github.com/riot/route), Riot's official client-side routing solution.

> Before starting, make sure you have a base application running, or read my previous article Setup [Riot + BeerCSS + Vite](https://dev.to/steeve/riotjs-material-design-how-to-setup-beercss-269c).

Client-side routing links the browser URL with the content on the page. When a user navigates the Riot application, the URL changes without requesting a new front-end from a server. **This is called a SPA**, for Single Page Applications: Riot handles all data updates and navigation without reloading the page, which makes the app rich and reactive! 

Let's create the simplest routing example; then, we will delve into advanced production usage.

## Basic Route

We aim to create the following app: a left drawer displaying links to different pages, and when a click happens on a link, the right section prints the corresponding page. The style is powered with the Material Design CSS [BeerCSS](https://www.beercss.com/):

![Riot application changing route on Firefox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yswq2kfk6rfeudzq36ao.gif)

Write the following code in `./index.riot`. The HTML comes from the [BeerCSS](https://www.beercss.com/#navigation-drawer) documentation, and I added RiotJS syntax for the logic:
```html
<index-riot>
    <router>
        <nav class="drawer left right-round border">
            <header>
                <nav>
                    <img class="circle" src="./examples/data/img-card.png"/>
                    <h6>Jon Snow</h6>
                </nav>
            </header>
            <!-- These links will trigger automatically HTML5 history events -->
            <a href="/">
                <i>inbox</i>
                <span class="max">Inbox</span>
                <b>24</b>
            </a>
            <a href="/favorite">
                <i>favorite</i>
                <span class="max">Starred</span>
                <b>3</b>
            </a>
            <a href="/sent">
                <i>send</i>
                <span class="max">Sent</span>
                <b>11</b>
            </a>
            <div class="medium-divider"></div>
            <a href="/subscription">
                <i>rocket</i>
                <span>Subscription</span>
            </a>
            <a href="/settings">
                <i>settings</i>
                <span>Settings</span>
            </a>
            
        </nav>
        <!-- Your application routes will be rendered here -->
        <span style="display:block;margin-left:20px;margin-top:20px">
            <route path="/"> <h2>Inbox</h2> </route>
            <route path="/favorite"> <h2>Starred</h2> </route>
            <route path="/sent"> <h2>Sent</h2> </route>
            <route path="/subscription"> <h2>Subscription</h2> </route>
            <route path="/settings"> <h2>Settings</h2> </route>
        </span>
    </router>
    <script>
        import { Router, Route } from '@riotjs/route'

        export default {
            components: { Router, Route }
        }
    </script>
</index-riot>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/examples/riot-route/index.basic.riot

This example uses two Components provided by riot-route: 
* **Router**: The `<router>` wraps the Riot application and automatically detects all the clicks on links that should trigger navigation change.
* **Route**: The `<route path="/some/route/:params">` renders the page content if the `path` attribute corresponds to the current URL path. The `path` can accept regex, or parameters, and you can <u>access the current route</u> with the **route** object:
```html
<route path="/:some/:route/:param"> {JSON.stringify(route.params)} </route>

<route path="/search(.*)">
  <!-- Assuming the URL is "/search?q=awesome" -->

  {route.searchParams.get('q')}
</route>
```
> Source Code from the [Riot-Route documentation](https://github.com/riot/route)

To access the current route in the Javascript section, it is possible to import the **route** object from '@riotjs/route':
```js
import { Router, Route, route } from '@riotjs/route'
```

## Advanced Route

Let's delve into advanced routing with the following requirements for a front-end:
* Show a 404 page if a URL path does not exist.
* Access query parameters into each page component.
* For each route, display a Riot Component as a page.
* Create a routing configuration file defining all routes, paths, and components.

![Riot application on Google Chrome with a routing showing 404 when the page does not exist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fxnepfn2ql0kzfm69hgr.gif)

In the first step, we will create 6 components, one for each page and another for the `404 Not Found` page. Components are located in the **pages** directory:
```yml
pages/p-favorite.riot
pages/p-inbox.riot
pages/p-sent.riot
pages/p-settings.riot
pages/p-subscription.riot
pages/p-not-found.riot
```
Each component has only one title `<h2>` tag, for instance, the `pages/p-sent.riot` component looks like this:
```html
<p-sent>
    <h2>Sent</h2>
</p-sent>
```
Or the `pages/p-not-found.riot` looks like:
```html
<p-not-found>
    <h2> 404 Page Not Found </h2>
</p-not-found>
```
Then, create a global routing configuration file in the **routes.js**. The file returns a list of pages, and each Page has a `name`, a `path` with a long regex, and a corresponding component:
```js
export default [
    {
      name     : 'Inbox',
      href     : '/',
      path     : '/(/?[?#].*)?(#.*)?',
      component: 'p-inbox',
      icon     : 'inbox'
    },
    {
      name     : 'Starred',
      href     : '/favorite',
      path     : '/favorite(/?[?#].*)?(#.*)?',
      component: 'p-favorite',
      icon     : 'favorite'
    },
    {
      name     : 'Sent',
      href     : '/sent',
      path     : '/sent(/?[?#].*)?(#.*)?',
      component: 'p-sent',
      icon     : 'send',
      separator: true
    },
    {
        name     : 'Subscription',
        href     : '/subscription',
        path     : '/subscription(/?[?#].*)?(#.*)?',
        component: 'p-subscription',
        icon     : 'rocket',
    },
    {
      name     : 'Settings',
      href     : '/settings',
      path     : '/settings(/?[?#].*)?(#.*)?',
      component: 'p-settings',
      icon     : 'settings'
    }
  ]
```
> Source code: https://github.com/steevepay/riot-beercss/blob/main/examples/riot-route/routes.js

The `<route>` Riot component will use the `path` attribute. Each regex is composed of 3 parts:
* `/settings`: Path of the page (Required static string)
* `(/?[?#].*)`: Query parameters (Optional group)
* `(#.*)?`: Fragment, a section within a page (Optional group)

Now, import the **routes.js**, and all components into the **index.riot** file: Define components into the `components:{}` Riot Object, and load the routes into the `state:{}` Object:
```html
<index-riot>
    <router>
        <nav class="drawer left right-round border">
            <header>
                <nav>
                    <img class="circle" src="./examples/data/img-card.png"/>
                    <h6>Jon Snow</h6>
                </nav>
            </header>
            <!-- Navigation bar created dynamically -->
            <template each={ page in state.pages }>
                <a href={ page.href }>
                    <i>{ page.icon }</i>
                    <span class="max">{ page.name }</span>
                </a>
                <div if={ page.separator === true } class="medium-divider"></div>
            </template>            
        </nav>
        <!-- Your application components/routes will be rendered here -->
        <span style="display:block;margin-left:20px;margin-top:20px">
            <route each={ page in state.pages  } path={ page.path }>
                <span is={ page.component } route={ route }></span>
            </route>
            <p-not-found if={ state.showNotFound } />
        </span>
    </router>
    <script>
        import { Router, Route, route, toRegexp, match } from '@riotjs/route';
        import pages from './routes.js'

        import pInbox from "./pages/p-inbox.riot";
        import pFavorite from "./pages/p-favorite.riot";
        import pSent from "./pages/p-sent.riot"
        import pSettings from "./pages/p-settings.riot"
        import pSubscription from "./pages/p-subscription.riot"
        import pNotFound from "./pages/p-not-found.riot"

        export default {
            components: { Router, Route, pInbox, pFavorite, pSent, pSettings, pSubscription, pNotFound },
            state: {
                pages,
                showNotFound: false
            },
            onMounted (props, state) {
                // ROUTING: create a stream on all routes
                this.anyRouteStream = route('(.*)')
                // ROUTING: check any route change to understand if the not found site should be displayed
                this.anyRouteStream.on.value((path) => {
                    this.update({ showNotFound: !this.state.pages.some(p => match(path.pathname, toRegexp(p?.path))) })  
                })
            },
            onUnmounted() {
                this.anyRouteStream.end()
            }
        }
    </script>
</index-riot>
```
> Source code: https://github.com/steevepay/riot-beercss/blob/main/examples/riot-route/index.advanced.riot

This code differs a lot compared to the Basic example; here are the major changes:
* To print a component for each page, a loop is created on `state.pages`. Within each `<route></route>`, the span HTML elements are used as Riot components by adding the **is** attribute:
```html
<route each={ page in state.pages  } path={ page.path }>
   <span is={ page.component } route={ route }></span>
</route>
```
* Navigation drawer links are also generated thanks to the route configuration, which is accessible with `state.pages`:
```html
<template each={ page in state.pages }>
   <a href={ page.href }>
      <i>{ page.icon }</i>
      <span class="max">{ page.name }</span>
   </a>
   <div if={ page.separator === true } class="medium-divider"></div>
</template> 
```
* The route object is used on the Javascript part to check if the current URL exists: A stream of routes is created to listen for route changes on the `onMounted () {}` [Riot lifecycle](https://riot.js.org/documentation/#lifecycle-callbacks). When a route changes, a function checks if the URL matches an existing route:
```js
onMounted (props, state) {
  this.anyRouteStream = route('(.*)')
  this.anyRouteStream.on.value((path) => {
     this.update({ showNotFound: !this.state.pages.some(p => match(path.pathname, toRegexp(p?.path))) })  
  })
},
onUnmounted() {
  // When the component is unmounted, the stream is stopped.
  this.anyRouteStream.end()
}
```
* For each component, the current route is passed as [Props](https://riot.js.org/documentation/#properties):
```
<span is={ r.component } route={ route }></span>
```
Within each component, the route is accessible with `props.route`, for instance the **c-inbox.riot** file:
```html
<p-inbox>
    <h2> Inbox </h2>
    <span>Filter: { props.route.searchParams.get('filter') }</span><br>
    <span>Order By: { props.route.searchParams.get('order') }</span>
</p-inbox>
```
Now you can use query parameters to request an API when the page is loaded in the `onMounted(){}` Riot lifecycle.

## Conclusion

Riot Route lets you create the navigation easily, with a syntax that is always close to HTML standards. 

One limitation of a Single Page Application is that it depends on a Backend/API to load the data. The browser/user has to wait until the content is rendered. To counter this issue, you can render HTML on the server side with [Riot-SSR](https://github.com/riot/ssr): the first request receives the page filled with data, so the user won't have to wait.

Have a great day! Cheers 🍻