Components and Hooks

This component can be used as a drop-in replacement for <a> tags. Internally, it renders an <a> tag so that statically rendered links will work even if JavaScript is disabled.

Props

active: boolean (optional)

Allows you to explicit enable or disable the activeClassName and activeStyle props.

activeClassName: string (optional)

Will be added to your link’s className if the app’s current URL matches the href prop, or if the active prop is set to true.

activeStyle: object (optional)

Will be merged into your link’s style if the app’s current URL matches the href prop, or if the active prop is set to true.

exact: bool (optional)

If true, the link will only be considered to “match” the current URL if it is an exact match.

By default, a partial match at the beginning of the current URL will also be considered a match. This facilitates nav links, which often need to be highlighted when child pages are active.

href: string (required)

The url to navigate to; identical to the href attribute of a HTML <a> tag.

prefetch: boolean (optional)

If specified, the linked page’s content will be fetched as soon as the link is rendered.

<NotFoundBoundary>

You can wrap a <NotFoundBoundary> around a view to show a “not found” message when no route can be found to be rendered by the view.

Props

render: (error: NotFoundError) => React.Node

Example

function Layout() {
  return (
    <NotFoundBoundary render={() =>
      <h1>Not Found</h1>
    }>
      <View />
    </NotFoundBoundary>
  )
}

<Router>

Your app’s <Router> component is responsible for subscribing to the latest navigation state, and passing that state to Navi’s other components.

By default, <Router> creates a navigation object internally, passing through the routes, basename, context and history props. However, if you’d like to define your navigation object manually — i.e. for static or server rendering support — then you can pass it in via the navigation prop.

Props

Your app’s Navigation object, as returned from createBrowserNavigation() or createMemoryNavigation()

By default, this will be created internally.

basename: string (optional)

The basename to use for the created navigation object. Only used when a navigation prop is not manually supplied.

children: React.ReactNode (optional)

If you don’t pass in any children, then a <View> will be rendered by default.

context: Context (optional)

The <Router> object will keep your navigation object’s context in sync with this prop by calling it’s setContext() method.

This will be used even if you supply a navigation prop.

hashScrollBehavior: 'smooth' | 'auto' (optional)

Specify whether to use native smooth scrolling using window.scroll(). By default, does not use smooth scrolling, as it is not supported by a number of browsers.

history: History (optional)

The history to use for the created navigation object. Only used when a navigation prop is not manually supplied.

routes: Matcher

The routes to use for the created navigation object. Only used when a navigation prop is not manually supplied.

<View>

Renders the content for the next matched view that hasn’t already been rendered.

If the content is a React element or component, then the children render function is optional, and that content will be rendered by default. Otherwise, you’ll need to provide a child function to specify how to render the route content.

Suspense

When the page initially loads, it can be the case that no content is available — even for previous routes. In this case, <View> will use React Suspense to pause execution until content is available. However, React requires you to wrap the suspended <View> with a <Suspense fallback>, so that you can decide what will be shown in place of the not-yet-ready content. If you’ve used React.lazy(), then this should be familiar — otherwise, check out the example below.

As React doesn’t yet support Suspense for server-side rendering, you’ll want to avoid using <Suspense> for statically rendered apps. Instead, wait for Navi’s initial content to render before calling ReactDOMServer.render() by creating a custom navigation object and using its navigation.getRoute() function, before passing the navigation object to your <Router> component’s navigation prop.

Props

children: (view, route) => React.ReactNode (optional)

If provided, the component’s children must be a render prop that receives the view’s value as its first argument, and the current Route as its second.

If a children prop is not provided, the default behavior is to render the next unrendered view from route.views.

Examples

Basic Usage

A <View> can be placed anywhere inside a <Router> component — it doesn’t have to be a direct descendent. You’ll often want your <View> to be nested within a <Suspense> component, or a <Layout> component, like so:

import { Router, View } from 'react-navi'

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

Usage with React Suspense

By wrapping your content in a React <Suspense> element, you can specify a fallback element to display while your app’s initial content is loading.

Note that until <Suspense> gets server-rendering support, this only makes sense for apps that are not pre-rendered with ReactDOMServer.render(). For static or server rendered apps, use navigation.getRoute() to wait for the initial content to load before hydrating the pre-rendered HTML with ReactDOM.hydrate().

import React, { Suspense } from 'react'
import { Router, View } from 'react-navi'
import Layout from './Layout'

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

useActive()

useActive(
  href: string | Partial<LocationObject>,
  options?: { exact?: boolean }
)

Returns true when the current route matches the specified href. You can use this along with useLinkProps() to create your own custom styled links.

Examples

// Will be true if currently at '/contacts'
let isViewingContactsIndex = useActive('/contacts')

// Will be true if viewing '/contacts' or any page nested within it
let isViewingContacts = useActive('/contacts', { exact: false })

useCurrentRoute()

A React Hook that allows you to access the latest non-busy Route, which contains all information that Navi knows about the current page, including:

  • data
  • title
  • url
  • views
  • etc.

You can also access the result of useCurrentRoute() by passing a child function to the <CurrentRoute> component, similar to the API for React context consumers.

Example

You can use useCurrentRoute() to retrieve the current page’s title, and display it as part of a <Layout> component.

import { useCurrentRoute } from 'react-navi'

function Layout({ children }) {
  let route = useCurrentRoute()

  return (
    <div className="Layout">
      <h1>{route.title}</h1>
      {children}
    </div>
  )
}

useLinkProps()

useLinkProps(options: {
  href: string | Partial<LocationObject>,
  onClick?: Function,
  prefetch?: boolean,
}): HTMLAnchorProps

This hook accepts a subset of the props that <Link> accepts, and returns an object with href, onClick, and any other props that you’d want to spread onto an <a>. You can then spread these props onto an <a> to make custom links components.

If the href is specified as a relative URL, it will be treated as relative to the current url. Navi normalizes the current URL so that trailing slashes have no effect.

Any unknown options will be passed through to the returned props object.

Use this along with useActive() to create components that change their styles depending on the current URL.

Examples

Say that you’ve built a <ListItem> component with styled-components. You’d like to make the whole item behave as a link — and you also want to highlight the item when the link points to the current page.

index.js
layout.js
list.js
routes.js
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom'
import { Router, View, useActive, useLinkProps } from 'react-navi'
import Layout from './layout'
import { List, ListItem } from './list'
import routes from './routes'

function ListItemLink({ children, href, ...rest }) {
  let active = useActive(href)
  let linkProps = useLinkProps({ href })
  return (
    <ListItem
      as="a"
      active={active}
      children={children}
      {...rest}
      {...linkProps}
    />
  )
}

ReactDOM.render(
  <Router routes={routes}>
    <Layout
      left={
        <List>
          <ListItemLink
            href="/hats"
            title="Hats"
            description="Don't get burned"
          />
          <ListItemLink
            href="/other"
            title="Other"
            description="Not-a-flamethrowers, etc."
          />
        </List>
      }
      right={
        <Suspense fallback={null}>
          <View />
        </Suspense>
      }
    />
  </Router>,
  document.getElementById('root')
)
Build In Progress

useLoadingRoute()

A React Hook that outputs the Route object for any page whose content is currently being fetched. If no page is currently being fetched, then it outputs undefined.

Use useLoadingRoute() to display a loading overlay when the user has requested a navigation that hasn’t completed yet.

You can also access the result of useLoadingRoute() by passing a child function to the <LoadingRoute> component, similar to the API for React context consumers.

Example

import { useLoadingRoute } from 'react-navi'

function Layout({ children }) {
  let loadingRoute = useLoadingRoute()

  return (
    <div className="Layout">
      <BusyIndicator show={!!loadingRoute} />
      { children }
    </div>
  )
}

useNavigation()

A React Hook that returns your app’s navigation store, which can be used to facilitate programmatic navigation.

You can also access the result of useNavigation() by passing a child function to the <NaviConsumer> component, similar to the API for React context consumers.

Example

You can use useNavigation() to programmatically navigate after a form is successfully submitted.

import React, { useState } from 'react'
import { useNavigation } from 'react-navi'

function EmailForm() {
  let navigation = useNavigation()
  let [email, setEmail] = useState()

  function handleSubmit() {
    navigation.navigate('/thanks')
  }

  return (
    <form onSubmit={handleSubmit} className="EmailForm">
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <button>Join 13,000 other subscribers</button>
    </div>
  )
}

useView()

Silimar to useViewElement(), this hook can act in place of the <View /> element. However, instead of returning a ready-to-go-element, it returns an object with the view’s raw content, a connect() function that you’ll need to wrap it with, and an assortment of other bits and pieces.

{
  chunks: Chunk[]
  connect: (node: React.ReactNode) => React.ReactElement<any>
  content: any
  element: React.ReactElement<any>
  final: boolean
  head: any
}

The main reason you’d want to use the useView() hooks is when your view content contains data that you need to manually convert to a React element. For example, here’s how it’s used in create-react-blog to extract a readingTime variable from the view content:

import { useCurrentRoute, useView } from 'react-navi'

function BlogPostLayout({ blogRoot }) {
  let { title, data, url } = useCurrentRoute()
  let { connect, content, head } = useView()
  let { MDXComponent, readingTime } = content

  // The content for posts is an MDX component, so we'll need
  // to use <MDXProvider> to ensure that links are rendered
  // with <Link>, and thus use pushState.
  return connect(
    <>
      {head}
      <article className={styles.container}>
        <ArticleMeta
          blogRoot={blogRoot}
          meta={data}
          title={title}
          url={url}
          readingTime={readingTime}
        />
        <MDXComponent />
      </article>
    </>,
  )
}

useViewElement()

Returns an element that renders exactly the same content that a <View /> element would render at the time that useViewElement() was called.

function App() {
  let view = useViewElement()

  return (
    <Layout>
      {view}
    </Layout>
  )
}

Generally, this behaves exactly the same as rendering a <View />. The main use for useViewElement() is in implementing animated transitions between routes. In this case, you’ll often need to render both the current view and the previous view at the same time. And while <View /> will always render the current view, it’s possilbe to store the view element returned by useViewElement() until it is no longer needed.

For example, here’s an example of animated transitions using react-spring and useViewElement():

app.js
card.js
index.js
routes.js
import React from 'react'
import { useCurrentRoute, useViewElement } from 'react-navi'
import { animated, useTransition } from 'react-spring'

export default function App() {
  let currentRoute = useCurrentRoute()
  let viewElement = useViewElement()
  let transitions = useTransition(viewElement, currentRoute.url.href, {
    from: { opacity: 0, transform: 'scale(0.5) translateY(-50%)' },
    enter: { opacity: 1, transform: 'scale(1) translateY(0)' },
    leave: { opacity: 0, transform: 'scale(0.5) translateY(50%)' },
  })

  return transitions.map(({ item, props: style, key, state }) =>
    <animated.div key={key} style={{
      ...style,
      position: 'absolute',
      top: 0,
      width: '100%',
    }}>
      {item}
    </animated.div>
  )
}
Build In Progress