Programmatic Navigation

There are times when you’ll want some code to navigate the user to another location — even if they haven’t just clicked a <Link>. In cases like this, you’ll need to call navigate() on your app’s navigation object. And to get the navigation object, you can just call the useNavigation() hook.

For example, here’s how you could programmatically navigate the user after they’ve submitted a form:

index.js
components.js
import React, { useState } from 'react'
import { useNavigation } from 'react-navi'

export function NewForm() {
  let [name, setName] = useState('Spartacus')

  // `useNavigation()` returns a navigation object
  let navigation = useNavigation()

  let handleSubmit = (e) => {
    e.preventDefault()

    // You can then call `navigation.navigate()` to navigate to a new page.
    navigation.navigate('/thankyou/'+encodeURIComponent(name))
  }

  return (
    <form onSubmit={handleSubmit}>
      <h1>Enter your name</h1>
      <input value={name} onChange={e => setName(e.target.value)} />
      <button>Ok</button>
    </form>
  )
}

export function Thankyou({ name }) {
  return (
    <h1>Thankyou, {name}!</h1>
  )
}
Build In Progress

Navi’s navigation.navigate() function returns a promise to the Route object of the new URL. This makes it perfect for use in form submit handlers that expect a promise as a return — like react-final-form.

Here’s another form handling example, but this time using react-final-form, and POSTing the form result to a map() handler so the form can be handled server-side if JavaScript is disabled.

index.js
components.js
import { map, mount, redirect, route } from 'navi'
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom'
import { Router } from 'react-navi'
import { Form, FormErrors, FormField, FormSubmitButton } from './components'

async function login(name) {
  alert('trying login')
  if (name === 'Spartacus') {
    throw new Error("I don't believe you.")
  }
}

const loginRoute = map(async req => {
  let state = {}

  // If the request fails, store the error in window.history.state so
  // that re-running the route will not retry the request.
  if (req.method === 'POST' && !req.state.error) {
    let name = req.body.name

    try {
      await login(name)
      return redirect('/thankyou/'+encodeURIComponent(name))
    }
    catch (error) {
      state.error = error && error.message
    }
  }

  return route({
    error: state.error,
    state,
    status: state.error ? 400 : 200,
    head: <title>Login</title>,
    view: <Login />
  })
})

function Login() {
  return (
    <Form method='POST' initialValues={{ name: 'Spartacus' }}>
      <h1>Login</h1>
      <FormErrors render={error =>
        error ? <div style={{color: 'red'}}>{error}</div> : null
      } />
      <label>
        Name:&nbsp;
        <FormField name='name' component='input' />
      </label>
      <FormSubmitButton type='submit'>Login</FormSubmitButton>
    </Form>
  )
}

export function Thankyou({ name }) {
  return (
    <h1>Thankyou, {name}!</h1>
  )
}

ReactDOM.render(
  <Suspense fallback={null}>
    <Router routes={
      mount({
        '/': loginRoute,
        '/thankyou/:name': map(req =>
          route({
            view: <Thankyou name={req.params.name} />
          })
        ),
      })
    } />
  </Suspense>,
  document.getElementById('root')
)
Build In Progress