npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@touchtribe/redux-router

v1.8.0

Published

Bare basic react router for usage with redux

Downloads

1

Readme

@touchtribe/redux-router

redux-router is a basic router for Redux applications.

While React Router is a great library, it makes it difficult to combine it with Redux patterns and near impossible to integrate it with Redux middlewares.

The router is inspired by redux-little-router. To understand the reasoning behind most of this router, check: "Let the URL do the Talking": Part 1 Part 2 Part 3

The principles behind the router are:

  • The URL is just another member of the state tree
  • URL changes are just plain actions
  • Route definitions should be easily customizable without having an impact on the actual implementation
  • Route definitions should be reusable throughout the app
  • Route definitions should come from a single source of truth

Usage

The router uses:

  • a middleware for:
    • intercepting navigation actions and calling their equivalent method in 'history'
    • intercepting URL changes, parsing them based on the specified patterns and dispatching locationChanged actions
  • a reducer for updating the state on URL changes
  • patterns for parsing the URL and translating it into a meaningful state-update
  • a Route component which renders children based on current route
  • a Link component which dispatch navigation actions when clicked

Middleware

createMiddleware(history, patterns) The middleware is responsible for:

  • intercepting navigation actions and calling their equivalent method in history
  • intercepting URL changes and dispatching locationChanged action

Paremeters:

  • history is one of the types from the history library
  • patterns is either
    • an array of patterns
    • an object having patterns as values
import { createStore, applyMiddleware } from 'redux'
import { createBrowserHistory } from 'history'
import { createMiddleware, createPattern } from '@touchtribe/redux-router'
import rootReducer from './reducers'

const history = createBrowserHistory()

const patterns = {
  Home: createPattern('/'),
  Contact: createPattern('/contact')
}

const routerMiddleware = createMiddleware(history, patterns)

const store = createStore(
  rootReducer,
  {},
  applyMiddleware(routerMiddleware)
)

Often, you'll want to update state or trigger side effects after loading the initial URL to ensure compatibility with other store enhancers.

import { initializeLocation } from '@touchtribe/redux-router'

// ...after creating your store and initializing other side-effect handlers
store.dispatch(initializeLocation(history.location))

Reducer

The router reducer works on the namespace router, which can also be access through routerReducer.toString() or String(routerReducer).

import { combineReducers } from 'redux'
import { routerReducer } from '@touchtribe/redux-router'
const rootReducer = combineReducers({
  router: routerReducer
})
// or using the routerReducer.toString() method
const rootReducer = combineReducers({
  [routerReducer]: routerReducer
})

Patterns

Patterns are definitions for URLs, which gets parsed using url-pattern

let pattern = createPattern('/users/:id')

See the documentation of url-pattern on how to use them.

React bindings and usage

  • <Route> component which renders children based on current route
  • <Link> component which dispatch navigation actions when clicked

<Route />

<Route> components conditionally render when the current location matches their pattern.

The simplest version of a route renders the children when a url matches the pattern

import { Route } from '@touchtribe/redux-router'
...
<Route pattern={'/user/:id'}>
  <p>This will render if the URL matches "/user/:id"
</Route>

pattern can also be an instance of UrlPattern (createPattern), which can be usefull for reusing patterns that are defined early in the process

import { Route, createPattern } from '@touchtribe/redux-router'

let Patterns = {
  Home: createPattern('/'),
  UserOverview: createPattern('/users/'),
  UserDetail: createPattern('/users/:id')
}
...
<Route pattern={Patterns.Home}>
  <p>This will render if the route matches "/user/:id"
</Route>

The <Route> component also accepts a component prop to render if it matches:

const UserDetailPage = (props) => <div>User Detail Page</div>
...
<Route pattern={'/user/:id'} component={UserDetailPage} />

For more control, children also accepts a render-function

<Route pattern={'/user/:id'}>
{({location, pattern, match, ...props}) => (
  match
  ? <p>this will render if the URL matches "/user/:id"
  : <p>this will render otherwise</p>
)}
</Route>

The idea behind redux-router doesn't allow for nested routes, since every route should come from a single source. While <Route> components can be nested, the patterns don't stack.

<Route pattern={'/users'}>
  <p>This will be rendered if the URL matches "/users"</p>
  <Route pattern={'/:id'}>
    <p>This will be rendered if the URL matches "/:id"</p>
  </Route>
  <Route pattern={'/users/:id'}>
    <p>This will be rendered if the URL matches "/users/:id"</p>
  </Route>
</Route>

<Route> makes basic component-per-page navigation easy:

<React.Fragment>
  <Route pattern={'/'} component={HomePage} />
  <Route pattern={'/users'} component={UserOverviewPage} />
  <Route pattern={'/users/:id'} component={UserDetailPage} />
</React.Fragment>

<Link>

The link component is easy to use and renders as <a href="...">...</a> by default

import { Link } from '@touchtribe/redux-router'

<Link href="/" className="blue-link">
  Click me
</Link>

It can also be used together with a pattern to promote reusability

...
<Link href={Patterns.UserOverview}>
  To user overview
</Link>

To change props when a link is active (matched), you can use the activeProps prop

className is a special case, in which className gets concatenated with activeProps.className

<Link href="/" className="link" activeProps={{className: 'link--active'}}>
  To homepage
</Link>

To render a Link as a different component, you can use the as prop:

the Component needs to propogate onClick for this to work

...
<Link href={Patterns.UserOverview} as={Button}>
  To user overview
</Link>

To add parameters to the URL, the query prop can be used. These will both be added as params for the Pattern as well as to the query-string

...
const UserOverviewPage = createPattern('/users/:userId')
<Link href={UserOverviewPage} query={{userId: 10}}>
  To user detail
</Link>

To use replace instead of push, the replaceState can be used:

...
<Link href={Patterns.UserOverview} replaceState>
  To user overview
</Link>

Actions

@touchtribe/redux-router providers the following action creators for navigation:

import { push, replace, go, back, forward, locationChanged, initializeLocation } from '@touchtribe/redux-router';

// to push a new route
push('/')
// to push a new route with a query
push('/', { userId: 10 })
// to push a new route using a pattern
const HomePage = createPattern('/')
push(HomePage)
// to push a new route using a pattern with params
const UserDetailPage = createPattern('/user/:userId')
push(UserDetailPage, { userId: 10 })

// replace and push use the same parameters
// to replace your current route
replace('/')

// Navigates forwards or backwards
go(3)
go(-2)

// equivalent of the browser back button
back()
// equivalent of the browser forward button
forward()

All action-types can be retrieved by using <action>.toString() or String(<action>), e.g.: String(push). This is necessary to listen for them in either your side-effects or your reducers:

const reducer = (state, action) => {
  switch(action.type) {
    case String(push): 
      ...
    case String(replace): 
      ...
    case String(initializeLocation): 
      ...
    case String(locationChanged):
      ...
  }
  return state
}

Helpers

createPattern(href)

To create patterns for reuse within the application

parseLocation(location, patterns)

Matches the current location against the given patterns and returns the information which would be reflected in the state:

{
  location,
  pattern,
  matches,
  params,
  hash
}