Getting Started

Navi lets you take advantage of React’s Suspense, Hooks and Error Boundary APIs to handle asynchronous routes declaratively.

This guide will walk you through the process of adding routing to a fresh create-react-app project, using Navi.

Basic Components

Navi’s <Router> component lies at the top of most Navi apps. It lets you add declarative, asynchronous routing to your React app. To get started, just render a <Router> in the index.js file generated by create-react-app. You can declare your routes with Navi’s mount(), route() and other matcher functions — like so:

import { lazy, mount, route } from 'navi'
import { Router } from 'react-navi'

// Define your routes
const routes =
  mount({    '/': route({      title: 'My Shop',
      getData: () => api.fetchProducts(),
      view: <ShopLandingPage />,
    }),
    '/products': lazy(() => import('./productsRoutes')),  })

// Pass them to a `<Router>`
<Router routes={routes}>  ...
</Router>

Ok, so you’ve got a <Router> with a couple routes, including a shop’s landing page and a lazily loadable /products URL. Next step: you need to decide where to render the current route’s view element. And to do that, you just plonk down a <View /> somewhere inside your <Router>; it could, for example, go inside a <Layout> component that renders some <Link> elements.

import { View } from 'react-navi'

ReactDOM.render(
  <Router routes={routes}>
    <Layout>
      <View />    </Layout>
  </Router>,
  document.getElementById('root')
)

Simple, huh? But waaait a minute… what if you view the lazily loadable /products URL? Then the route will be loaded via an import(), which returns a Promise, and so at first there’ll be nothing to render. Luckily, React’s new <Suspense> feature lets you declaratively wait for promises to resolve. So just wrap your <View> in a <Suspense> tag, and you’re off and racing!

index.js
product.js
Landing.js
Layout.js
api.js
styles.css
import { mount, route, lazy } from 'navi'
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom'
import { Router, View } from 'react-navi'
import api from './api'
import Landing from './Landing'
import Layout from './Layout'

const routes =
  mount({
    '/': route({
      title: "Hats 'n' Flamethrowers 'r' Us",
      getData: () => api.fetchProducts(),
      view: <Landing />,
    }),
    '/product': lazy(() => import('./product')),
  })

ReactDOM.render(
  <Router routes={routes}>
    <Layout>
      <Suspense fallback={null}>
        <View />
      </Suspense>
    </Layout>
  </Router>,
  document.getElementById('root')
)
Build In Progress

Routing Hooks

Did you notice how your route defined a getData() function along with its view?

route({
  title: 'My Shop',
  getData: () => api.fetch('/products'),
  view: <Landing />,
})

How do you access the data? With React hooks!

Navi’s useCurrentRoute() hook can be called from any function component that is rendered within the <Router> tag. It returns a Route object that contains everything that Navi knows about the current URL — including any data you’ve declared on you routes.

index.js
product.js
Landing.js
Layout.js
api.js
styles.css
import React from 'react'
import { Link, useCurrentRoute } from 'react-navi'

export default function Landing() {
  // useCurrentRoute returns the lastest loaded Route object
  let route = useCurrentRoute()
  let data = route.data
  let productIds = Object.keys(data)

  console.log('views', route.views)
  console.log('url', route.url)
  console.log('data', route.data)
  console.log('status', route.status)
  
  return (
    <ul>
      {productIds.map(id => 
        <li key={id}>
          <Link href={`/product/${id}`}>{data[id].title}</Link>
        </li>
      )}
    </ul>
  )
}
Build In Progress

    Ok. So far, so good. But imagine that you’ve just clicked a link to /products — which is dynamically imported. It’s going to take some time to fetch the route, so what are you going to display in the meantime?

    Well, the first option is to use <Suspense> to show a fallback while it’s loading. But this has the problem of jumping around all over the place as it hides and shows loading indicators.

    terrible looking loading

    What you’d really like to do is to display a loading bar over the current page while the next route loads… well, unless the transition only takes 100ms. Then you probably just want to keep displaying the current page until the next one is ready, because showing a loading bar for only 100ms doesn’t look great either.

    loading indicator with no delay

    So how do you show a loading indicator, but only when the route doesn’t load instantly? This would usually be incredibly complicated to accomplish, but Navi makes it simple: you just use the useLoadingRoute() hook.

    index.js
    product.js
    Landing.js
    Layout.js
    api.js
    styles.css
    import BusyIndicator from 'react-busy-indicator@1.0.0'
    import React from 'react'
    import { Link, useLoadingRoute } from 'react-navi'
    
    export default function Layout({ children }) {
      // If there is a route that hasn't finished loading, it can be
      // retrieved with `useLoadingRoute()`.
      let loadingRoute = useLoadingRoute()
    
      return (
        <div className="Layout">
          {/* This component shows a loading indicator after a delay */}
          <BusyIndicator isBusy={!!loadingRoute} delayMs={200} />
          <header className="Layout-header">
            <h1 className="Layout-title">
            <Link href='/'>
              Hats 'n' Flamethrowers 'r' Us
            </Link>
            </h1>
          </header>
          <main>
            {children}
          </main>
        </div>
      )
    }
    
    Build In Progress

    Here’s how it works: useCurrentRoute() returns the most recent completely loaded route. And useLoadingRoute() returns any requested-but-not-yet-completely-loaded route. Or if the user hasn’t just clicked a link, it returns undefined. Simple, huh?

    Error Boundaries

    One of the things about asynchronous data and views is that sometimes they fail to load. Luckily, React has a great tool for dealing with things that throw errors: Error Boundaries.

    Let’s rewind for a moment to the <Suspense> tag that wraps your <View />. When <View /> encounters a not-yet-loaded route, it throws a promise, which effectively asks React to please show the fallback for a moment. You can imagine that <Suspense> catches that promise, and then re-renders its children once it resolves.

    Similarly, if <View /> finds that getView() or getData() have failed, then the View component will throw an error. In fact, if the router encounters a 404-not-found error, then <View /> will throw that, too. These errors can be caught by Error Boundary components. For the most part, you’ll need to make your own error boundaries, but Navi includes a <NotFoundBoundary> that can catch Not Found errors for you and show a fallback message instead:

    index.js
    product.js
    Landing.js
    Layout.js
    api.js
    styles.css
    import BusyIndicator from 'react-busy-indicator@1.0.0'
    import React from 'react'
    import { Link, NotFoundBoundary, useLoadingRoute } from 'react-navi'
    
    export default function Layout({ children }) {
      // If there is a route that hasn't finished loading, it can be
      // retrieved with `useLoadingRoute()`.
      let loadingRoute = useLoadingRoute()
    
      return (
        <div className="Layout">
          {/* This component shows a loading indicator after a delay */}
          <BusyIndicator isBusy={!!loadingRoute} delayMs={200} />
          <header className="Layout-header">
            <h1 className="Layout-title">
            <Link href='/'>
              Hats 'n' Flamethrowers 'r' Us
            </Link>
            </h1>
          </header>
          <main>
            <NotFoundBoundary render={renderNotFound}>
              {children}
            </NotFoundBoundary>
          </main>
        </div>
      )
    }
    
    function renderNotFound() {
      return (
        <div className='Layout-error'>
          <h1>404 - Not Found</h1>
        </div>
      )
    }
    
    Build In Progress

    What’s Next?

    You can get a long way with Navi by just using these components and a few basic matcher functions. However, if you take a look underneath all the components, Navi is actually written in pure JavaScript — that’s why it’s split into two packages:

    • navi contains a pure JavaScript implementation of the core routing code
    • react-navi gives you a thin wrapper around the core that’s designed specifically for React

    If you want to understand the full set of possibilities that Navi opens up, you’ll want to understand the core concepts of Navi itself — requests, routes, and matchers. As it happens, you’ve already run into each of these concepts in this page — and we’ll cover them in a moment. But first, let’s take a brief look at URL parameters.