@s-ui/react-router

1.16.0 • Public • Published

sui-react-router

Set of navigational components that compose declaratively with your application.

Definition

Installation

npm install @s-ui/react-router

Routes Setup

You must define a React Element with your application routes tree. Check the Configuration Components documentation to see the available components and their configuration.

// routes.js file
import { IndexRoute, Redirect, Route } from '@s-ui/react-router'

export default (
  <Route>
    <Redirect from="/" to="/es/" />
    <Route path="/:lang" component={App}>
      <IndexRoute getComponent={loadHomePage} />
      <Route
        path="products"
        getComponent={loadProductsPage}
      />
    </Route>
  </Route>
)

### Client Setup

In order to be able to use the @s-ui/react-router on the client you should wrap your application with the <Router> component. It will provide the needed context to be able to use components like <Link> inside your app.

If you only need Client Side Rendering

/* Basic example by using directly Router */
import {Router} from '@s-ui/react-router'
import routes from './routes'

// you're ready to render (or hydrate if you already has rendered your app in the server)
// you MUST wrap your app with the `<Router>` app that will provide the needed context
ReactDOM.hydrate(
  <Router>{routes}</Router>,
  document.getElementById('app')
)

If you are doing Server Side Rendering

/* Advanced example using match method */
import {match, Router} from '@s-ui/react-router'
import routes from './routes'

match({routes}, (err, redirectLocation, renderProps) => {
  // if we have an error, log it and do nothing more
  if (err) {
    console.error(err)
    return
  }

  // if some <Redirect> has been matched, then we will have the `redirectLocation` info
  if (redirectLocation && redirectLocation.pathname) {
    window.location = redirectLocation.pathname
    return
  }

  // you're ready to render (or hydrate if you already has rendered your app in the server)
  // you MUST wrap your app with the `<Router>` app that will provide the needed context
  ReactDOM.hydrate(
    <Router {...renderProps} />,
    document.getElementById('app')
  )
})

Server Setup

import {match, Router} from '@s-ui/react-router'
import {renderToString} from 'react-dom/server'
import routes from './routes'

export default (req, res, next) => {
  const {url, query} = req

  match({location: req.url, routes}, async (error, redirectLocation, renderProps) => {
    if (error) return next(error)
    if (redirectLocation) return res.redirect(301, redirectLocation.pathname)
    if (!renderProps) return next()

    const renderedApp = renderToString(<Router {...renderProps} />)
    res.status(200).send(renderedApp)
  })

API Reference

Components

<Router>

Primary component of @s-ui/react-router. It keeps your UI and the URL in sync and provides your application with the needed React context.

Props

children (required)

One or many <Route>s, <Redirect>s and one <IndexRoute>. When the history changes, <Router> will match a branch of its routes, and render their configured components, with child route components nested inside the parents.

history

The history the router should listen to. Typically browserHistory on the client. In server, for example, it would be memoryHistory.

import { browserHistory } from 'react-router'
ReactDOM.render(<Router history={browserHistory} />, node)

The primary way to allow users to navigate around your application. <Link> will render a fully accessible anchor tag with the proper href.

A <Link> can know when the route it links to is active and automatically apply an activeClassName and/or activeStyle when given either prop. The <Link> will be active if the current route is either the linked route or any descendant of the linked route.

Props

to

A location destination. Usually this is a string or a function, with the following semantics:

  • If it's a string it represents the absolute path to link to, e.g. /users/123 (relative paths are not supported).
  • If it's a function, it receives the location as a parameter and it must return the string following the rules of the previous point.
  • If it is not specified, an anchor tag without an href attribute will be rendered.

Note: @s-ui/react-router currently does not manage scroll position, and will not scroll to the element corresponding to hash.

// String location descriptor.
<Link to="/hello">
  Hello
</Link>

// Function returning location descriptor.
<Link to={location => `/hello?name=${location.query.name}`}>
  Hello
</Link>

// This will still work and render and anchor <a> without href
<Link>
  Hello
</Link>
activeClassName

The className a <Link> receives when its route is active. No active class by default.

activeStyle

The styles to apply to the link element when its route is active.

onClick(e)

A custom handler for the click event. Works just like a handler on an <a> tag - calling e.preventDefault() will prevent the transition from firing, while e.stopPropagation() will prevent the event from bubbling.

onlyActiveOnIndex

If true, the <Link> will only be active when the current route exactly matches the linked route.

innerRef

Allows access to the underlying ref of the component.

others

You can also pass props you'd like to be on the <a> such as a title, id, className, etc.

Example

Given a route like <Route path="/users/:userId" />:

<Link to={`/users/${user.id}`} activeClassName="active">{user.name}</Link>
// becomes one of these depending on your History and if the route is
// active
<a href="/users/123" class="active">Michael</a>

// change the activeClassName
<Link to={`/users/${user.id}`} activeClassName="current">{user.name}</Link>

// change style when link is active
<Link to="/users" style={{color: 'white'}} activeStyle={{color: 'red'}}>Users</Link>

const refCallback = node => {
  // `node` refers to the mounted DOM element or null when unmounted
}
<Link to="/" innerRef={refCallback} />

Route

RegExp

Warning: If you solve a problem with a RegExp, you end up with two problems. The original and the RegExp.

You can define a route in your application using a RegExp. And in this way simplify in certain scenarios the number of Route components you have to define.

regexp
<Route regexp={/\/(?<language>[a-z]+)\/user\/(?<userId>[0-9]+)$/} component={Component} />

Here we see that in addition to being able to match a regexp, you can manage to capture fragments of the URL using named groups. These captures you will find them in the params object like any other parameter. It is VERY important that you are careful when using a RegExp, because it has to be as inclusive as possible. This means that if you use it and the page doesn't work it is very likely that your regex will not consume the entire URL.

Hooks

useLocation

Returns the current location object. This is useful any time you need to know all the information of the current URL, like the pathname, the querystring with search or the host.

For example, you could create a hook to send a page view to your analytics tracker:

import {useLocation} from '@s-ui/react-router'

function usePageViewToGoogleAnalytics() {
  const location = useLocation()

  useEffect(() => {
    ga.send(['pageview', location.pathname])
  }, [location])

  return null
}

useRouter

Provides you access to the router object that contains relevant data and methods regarding routing. Most useful for imperatively transitioning around the application.

push(pathOrLoc)

Transitions to a new URL, adding a new entry in the browser history.

router.push('/users/12')

// or with a location descriptor object
router.push({
  pathname: '/users/12',
  query: { modal: true },
  state: { fromDashboard: true }
})
replace(pathOrLoc)

Identical to push except replaces the current history entry with a new one.

go(n)

Go forward or backward in the history by n or -n.

goBack()

Go back one entry in the history.

goForward()

Go forward one entry in the history.

createPath(pathOrLoc, query)

Stringifies the query into the pathname, using the router's config.

createHref(pathOrLoc, query)

Creates a URL, using the router's config. For example, it will add #/ in front of the pathname for hash history.

isActive(pathOrLoc, indexOnly)

Returns true or false depending on if the pathOrLoc is active. Will be true for every route in the route branch matched (child route is active, therefore parent is too), unless indexOnly is specified, in which case it will only match the exact path.

A route is only considered active if all the URL parameters match, including optional parameters and their presence or absence.

However, only explicitly specified query parameters will be checked. That means that isActive({ pathname: '/foo', query: { a: 'b' } }) will return true when the location is /foo?a=b&c=d. To require that a query parameter be absent, specify its value as an explicit undefined, e.g. isActive({ pathname: '/foo', query: { a: 'b', c: undefined } }), which would be false in this example.

useParams

Extract the matched params in a path in any place of your tree, in order

import {useParams} from '@s-ui/react-router'

function BlogPost() {
  // We can call useParams() here to get the params
  // matched on the path `/posts/:category/:slug`
  const { category, slug } = useParams()
}

// from this router heriarchy
ReactDOM.render(
  <Router>
    <Route path="/posts/:category/:slug" component={BlogPost} />
  </Router>
)

Configuration Components

<Route>

A <Route> is used to declaratively map routes to your application's component hierarchy.

Props

path

The path used in the URL.

It will concat with the parent route's path unless it starts with /, making it an absolute path.

If left undefined, the router will try to match the child routes.

component

A single component to be rendered when the route matches the URL. It can be rendered by the parent route component with props.children.

const routes = (
  <Route path="/" component={App}>
    <Route path="groups" component={Groups} />
    <Route path="users" component={Users} />
  </Route>
)

function App ({ children }) {
  return (
    <section>
      {/* children will be either <Users> or <Groups> depending on path */}
      {children}
    </section>
  )
}
getComponent(nextState, callback)

Same as component but asynchronous, useful for code-splitting.

You can pass a Promise, in which case you should not use the callback function and instead use resolve like you normally would. You can also pass an async function, which is a Promise under the hood, and just return like normal. Any thrown exception will report an error to the router.

callback signature

cb(err, component)

<Route path="courses/:courseId" getComponent={(nextState, cb) => {
  // do asynchronous stuff to find the components
  cb(null, Course)
}} />

<Redirect>

A <Redirect> sets up a redirect to another route in your application to maintain old URLs.

Props

from

The path you want to redirect from, including dynamic segments.

to

The path you want to redirect to.

query

By default, the query parameters will just pass through but you can specify them if you need to.

// Say we want to change from `/profile/123` to `/about/123`
// and redirect `/get-in-touch` to `/contact`
<Route component={App}>
  <Route path="about/:userId" component={UserProfile} />
  {/* /profile/123 -> /about/123 */}
  <Redirect from="profile/:userId" to="about/:userId" />
</Route>

Note that the <Redirect> can't be placed anywhere in the route hierarchy, so they can't be used as relative paths:

// ⚠️ this won't work
<Route path="course/:courseId" component={Course}>
  <Route path="dashboard" component={Dashboard} />
  <Redirect from="home" to="dashboard" />
</Route>

// ✅ this would be the correct way to use it
<Route>
  <Route path="course/:courseId" component={Course}>
    <Route path="dashboard" component={Dashboard} />
  </Route>
  <Redirect from="/course/:courseId/home" to="/course/:courseId/dashboard" />
</Route>

<IndexRoute>

An <IndexRoute> allows you to provide a default "child" to a parent route when visitor is at the URL of the parent.

To illustrate the use case for IndexRoute, imagine the following route config without it:

<Router>
  <Route path="/" component={App}>
    <Route path="accounts" component={Accounts}/>
    <Route path="statements" component={Statements}/>
  </Route>
</Router>

When the user visits /, the App component is rendered, but none of the children are, so this.props.children inside of App will be undefined. To render some default UI you could easily do {this.props.children || <Home/>}.

But now Home can't participate in routing. You render in the same position as Accounts and Statements, so the router allows you to have Home be a first class route component with IndexRoute.

<Router>
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="accounts" component={Accounts}/>
    <Route path="statements" component={Statements}/>
  </Route>
</Router>

Now App can render {this.props.children} and we have a first-class route for Home that can participate in routing.

Keep in mind only one IndexRoute will be matched through your route tree.

Props

All the same props as Route except for path.

Route Components

A route's component is rendered when that route matches the URL. The router will inject the following properties into your component when it's rendered:

Injected Props

location

The current location.

params

The dynamic segments of the URL.

route

The route that rendered this component.

router

Contains methods relevant to routing. Most useful for imperatively transitioning around the application.

routes

The routes registered with the router.

routeParams

Alias for params

children

The matched child route element to be rendered.

Example
render((
  <Router>
    <Route path="/" component={App}>
      <Route path="groups" component={Groups} />
      <Route path="users" component={Users} />
    </Route>
  </Router>
), node)

function App (props) {
  return (
    <div>
      {/* this will be either <Users> or <Groups> */}
      {props.children}
    </div>
  )
}

Histories

By default, @s-ui/react-router automatically detects the correct history depending on your environment but you could still access to these histories in case you need them.

browserHistory

browserHistory uses the HTML5 History API when available, and falls back to full refreshes otherwise. browserHistory requires additional configuration on the server side to serve up URLs, but is the generally preferred solution for modern web pages.

createMemoryHistory([options])

createMemoryHistory creates an in-memory history object that does not interact with the browser URL. This is useful for when you need to customize the history object used for server-side rendering, for automated testing, or for when you do not want to manipulate the browser URL, such as when your application is embedded in an <iframe>.

Utilities

match({ routes, location, [history], [...options] }, cb)

Note: You probably don't want to use this in a browser unless you're doing server-side rendering of async routes.

This function is to be used for server-side rendering. It matches a set of routes to a location, without rendering, and calls a callback(error, redirectLocation, renderProps) when it's done.

The function will create a history for you, passing the additional options along to create it. These options can include basename to control the base name for URLs, as well as the pair of parseQueryString and stringifyQuery to control query string parsing and serializing. You can also pass in an already instantiated history object, which can be constructed however you like.

The three arguments to the callback function you pass to match are:

  • error: A Javascript Error object if an error occurred, undefined otherwise.
  • redirectLocation: A Location object if the route is a redirect, undefined otherwise.
  • renderProps: The props you should pass to the routing context if the route matched, undefined otherwise.

If all three parameters are undefined, this means that there was no route found matching the given location.

withRouter(Component, displayName)

A HoC (higher-order component) that wraps another component to enhance its props with router props.

withRouterProps = {
  ...componentProps,
  router,
  params,
  location,
  routes
}

Pass in your component and it will return the wrapped component.

You can explicit specify router as a prop to the wrapper component to override the router object from context.

Options

displayName

You could define a manual displayName for your component. If not provided, it will use withRouter(wrappedComponentDisplayname), where wrappedComponentDisplayname is the displayName (or name of the function/class as a fallback) of the enhanced component.

const WrappedComponent = withRouter(MyComponent, { displayName: 'AwesomeComponentWithRouter' })
WrappedComponent.displayName // AwesomeComponentWithRouter

const WrappedComponent = withRouter(MyComponent)
WrappedComponent.displayName // withRouter(MyComponent)

Next features

  • [ ] Upgrade to History v6: In order to improve bundle size and avoid naming collisions with React hooks.

Readme

Keywords

none

Package Sidebar

Install

npm i @s-ui/react-router

Weekly Downloads

2,063

Version

1.16.0

License

MIT

Unpacked Size

110 kB

Total Files

47

Last publish

Collaborators

  • daniel.perez.ext
  • albert.peiro
  • frandelacasa-adevinta
  • mariapaula.forero.ext
  • hpintos_adevinta
  • sziauberyte
  • victor.perez.adevinta
  • oscar-raig-adevinta
  • carlos.gonzalezl
  • carolina.mallo.ext
  • david.nieto
  • ferran.simon
  • xavi_conejo
  • sergi.quintela
  • jamile.radloff
  • xavi_ballestar
  • luz_adv
  • ignacio.rodriguez
  • carlosvillu-adevinta
  • diegomr
  • arturo.vicente
  • adria.velardos
  • emiliovz
  • dann41
  • ruben-martin
  • pol.valls
  • cristina.rodriguez.duque
  • sergio.escano
  • marc.benito
  • azahara
  • patricio.sartore
  • giovanny.sayas.ext
  • david.cuadrado.ext
  • alex.castells
  • beatrizip
  • pablo.rey-adevinta
  • sergiocollado
  • mango.sanchezredondo
  • cristhianb
  • alisa_bayanova
  • davidmartin2108
  • ferrangbtw
  • estefania_garcia
  • belen.santos
  • alfredo.arronte
  • joanleon-adv
  • giodelabarrera.adevinta
  • luis-garrido
  • aitor.rodriguez
  • jordi.munoz
  • oscar_ramirez
  • ignacio_navarro
  • a.ferrer
  • gfabregoadv
  • izeller
  • salvador.juan
  • oriol.egea
  • ivanmlaborda
  • alejandro.ferrante
  • pa.chruscinski.ext
  • isabelgomez87
  • jenifer.lopez
  • daniela.aguilera
  • pablogs
  • javier.miguel
  • oscar.gomez
  • marian.lucaci
  • alverd004
  • oriol.puig
  • nacho_torrella
  • xavi.murcia
  • chloe.leteinturier
  • javiauso
  • alfredo.zimperz
  • francisco.ruiz.lloret
  • fermin.adevinta
  • andresadv
  • schibstedspain