Routing

Routing in your components is possible via sandstone/router module. Sandstone router is a modern client side router that provides Express-like route handling, supports history API and hash mode and provides some higher order stuff. Let's go through everything.

Basic usage

Definign routes in your components is very simple:

import { WebComponent } from 'sandstone';
import { Router } from 'sandstone/router';

class MyElement extends WebComponent{
    componentDidMount = () => {
        Router.on('/profile/:id', this.handleRouteChange);
    }

    handleRouteChange = (url) => {
        console.log('Route changed! ID:', url.params.id); // "Route changed! ID: 123"
    }

    changeRoute = () => {
        Router.push('/profile/123');
    }

    render() {
        return <button onClick={this.changeRoute}>Change Route</button>
    }
}

Your route handler function will receive a url object with the following structure:

  • params - object with url params
  • path - Full path string
  • query - Parsed object from query string

Router methods and properties

Besides push and on that we've already seen, router provides a few additional methods and configuration options.

Router.mode

  • Default: 'hash'

A string indicating router's operation mode. Can be "hash" or "history". To configure router for a different mode, just assign a new value to it:

Router.mode = 'history';

Keep in mind that this will configure the entire router, so you should only do it once at the top level of your app (before you register any of your components).

Router.root

  • Default: '/'

A string indicating router's root path. Same as with mode, just assign a new value to it to set:

Router.root = '/settings';

This should also be configured once at the top level of your app.

on(route, handlerFunction, persist)

Define a new handler for a route. Optional persist argument will prevent a handler from flushing if Router.flushHandlers is called.

Calling Router.on will return a string ID which you can use to remove handlers later if needed.

Usage:

Router.on('/profile/:id', (url) => {
    console.log(url.params.id);
})

removeHandler(id)

Removes previously defined route handler by it's ID.

Usage:

Router.removeHandler('_mp6vmcfda');

flushHandlers()

Removes all handlers from the router except ones that were defined with persist flag.

dangerouslyFlushRouter()

Completely resets router to it's default state. Root is set to '/', mode to 'hash' and all route handlers are removed (even persisted ones).

You will most likely never need to use this method. If you must, use with caution.

push(path)

Navigate to a new route. Uses pushState when in history mode.

Usage:

Router.push('/path/to/something');

replace(path)

Navigate to a new route and replace the history state instead of pushing.

Note: Only available in history mode.

Usage:

Router.replace('/path/to/something');

Routerize decorator

Defining routes explicitly isn't always convenient. Some times you just want to watch what happens and do stuff based on that. For these use cases, Sandstone provides a @routerize decorator:

import { WebComponent } from 'sandstone';
import { routerize } from 'sandstone/router';

@routerize
class MyElement extends WebComponent{
    render(props) {
        // props.router.state is the same object that is passed to route handlers
        return <h1>Hello route {props.router.state.path}</h1>
    }
}

Here's the structure of this.props.router prop that @routerize gives you:

{
    on(),
    push()
    replace(),
    flushHandlers(),
    dangerouslyFlushRouter(),
    root,
    state: {
        path,
        params: {},
        query: {},
    },
}

This will be updated on every route change, so you can rely on the prop directly, without needing to specify route handlers.

Note: Due to how router works by default, your component will be re-rendered on mount when routerized. It's usually not a big deal, but if it is, you should use custom shallow router instead.

<Route /> component

If you only need to watch routes to change your rendered content, you can use <Route /> component to do that.

Usage:

import { WebComponent } from 'sandstone';
import { Route } from 'sandstone/router';

class MyElement extends WebComponent{
    render(props) {
        return (
            <div>
                <Route path="/">
                    <h1>Home Page</h1>
                </Route>
                <Route path="/settings">
                    <h1>Settings</h1>
                </Route>
            </div>
        )
    }
}

Besides child node, Route also supports passing a component prop for render. If you do this, rendered component will automatically be routerized. You can bypass this behavior by using shallow prop.

import { WebComponent } from 'sandstone';
import { Route } from 'sandstone/router';
import FancyHeader from './FancyHeader';

class MyElement extends WebComponent{
    render(props) {
        return (
            <div>
                <Route path="/" component={FancyHeader} />
                <Route path="/settings" component={FancyHeader} shallow />
            </div>
        )
    }
}

In the above example, FancyHeader component on route / will get this.props.router, but the one on /settings route won't.

Advanced features

Custom Routers

In some cases you need to have different router configurations for different components. Sandstone allows you to crete custom routers for this.

Default Router is an instance of a SandstoneRouter class, which you can reuse or extend.

import { SandstoneRouter } from 'sandstone/router';

class SafeRouter extends SandstoneRouter {
    constructor(options) {
        super(options);

        this.dangerouslyFlushRouter = () => throw new Error("Safe router doesn't allow flushing");
        this.flushHandlers = () => throw new Error("Safe router doesn't allow flushing");
    }
}

const MyCustomRouter = new SafeRouter({ shallow: true, mode: 'history' });

Custom router options

  • shallow - Shallow mode. If this is set, router won't fire handlers on initial page load.
  • mode - Set default mode. Can be 'hash' or 'history'.
  • root - Set custom default root

Routerizing with custom router

The @routerize decorator uses default router instance. If you want to routerize your component with your custom router, you can use routerizeWith decorator instead:

import { WebComponent } from 'sandstone';
import { SandstoneRouter, routerizeWith } from 'sandstone/router';

const ShallowRouter = new SandstoneRouter({ shallow: true });

@routerizeWith(ShallowRouter)
class MyElement extends WebComponent{
    render(props) {
        return <h1>...</h1>
    }
}

Using custom routers with <Route />

You can easily use custom routers with <Route /> component by simply providing it as a customRouter prop:

import { WebComponent } from 'sandstone';
import { SandstoneRouter, Route } from 'sandstone/router';
import FancyHeader from './FancyHeader';

const ShallowRouter = new SandstoneRouter({ shallow: true });

class MyElement extends WebComponent{
    render(props) {
        return (
            <div>
                <Route path="/" component={FancyHeader} customRouter={ShallowRouter} />
            </div>
        )
    }
}

Server Side Rendering

It's important to understand that Sandstone router is a client side router. You should not use it in the server rendered parts of your app. Everything in and after componentDidMount() is fine, but everything before it will throw an error.

Also @routerize and @routerizeWirth decorators will set the props.router.path to null by default on the server.

SSR with <Route />

Route component supports initialPath prop that will be used for server side rendering.

import { WebComponent } from 'sandstone';
import { Route } from 'sandstone/router';
import FancyHeader from './FancyHeader';

class MyElement extends WebComponent{
    render(props) {
        return (
            <div>
                <Route path="/" initialPath="/" component={FancyHeader} />
                <Route path="/settings" initialPath="/" component={FancyHeader} />
            </div>
        )
    }
}

In the above example, first route will be rendered on the server, but second one won't.

Next: Dev Tools →

results matching ""

    No results matching ""