• Stars
    star
    152
  • Rank 244,685 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 8 years ago
  • Updated over 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A router for Redux Saga

Redux Saga Router

Travis branch npm

A router for Redux Saga

Redux Saga Router gives you a saga for handling clientside routes in your Redux Saga application. This affords you a perfect way to manage side effects or dispatch Redux actions in response to route changes.

Table of Contents

Install

Yarn or npm.

yarn add redux-saga-router
npm install --save redux-saga-router

Usage

Redux Saga Router comes equipped with a router saga and two history strategies, createBrowserHistory and createHashHistory.

The router saga expects a history object and a routes object with key-value pairs of route paths to other sagas. It also takes an optional third argument with additional options.

To create a history object, you can use createBrowserHistory or createHashHistory. createBrowserHistory uses HTML5 pushState while createHashHistory uses (you guessed it) hashes, which is perfect for older browsers. These two history creation functions in fact come from the history library.

import { call, fork, put } from 'redux-saga';
import { router, createBrowserHistory } from 'redux-saga-router';

const history = createBrowserHistory();

const routes = {
  '/users': function* usersSaga() {
    const users = yield call(fetchUsers);
    yield put(setUsers(users));
  },

  '/users/:id': function* userSaga({ id }) {
    const user = yield call(fetchUser, id);
    yield put(setCurrentUser(user));
  },
};

function* mainSaga() {
  const data = yield call(fetchInitialData);

  yield put(ready(data));

  // The recommended way is to `fork` the router, but you can delegate with
  // yield* too
  yield fork(router, history, routes);
}

Behavior

Redux Saga Router will spawn the first matching route saga. When the location changes, the current running saga will be cancelled. As such, you might want to clean up your saga in that event.

If you wish to avoid your saga's being cancelled, you can spawn a sub saga in your route saga like the following:

const routes = {
  '/': function* homeSaga() {
    yield spawn(subSaga);
  },
};

In the event of an unhandled error occurring in one of your sagas, the error will stop the running saga and will not propagate to the router. That means that your application will continue to function when you hit other routes. That also means you should ensure you handle any potential errors that could occur in your route sagas.

Routes

Routes may be expressed as either an object or an array with the main difference being that the array form preserves order and, therefore, the precedence of routes.

const objectFormRoutes = {
  '/foo': fooHandler,
  '/bar': barHandler,
};

const arrayFormRoutes = [
  { pattern: '/foo', handler: fooHandler },
  { pattern: '/bar', handler: barHandler },
];

Exact Matching

This route will only match /foo exactly.

const routes = {
  '/foo': saga,
};

Path Parameters

You can capture dynamic path parameters by prepending them with the : symbol. The name you use will be assigned to a property of the same name on a parameters object that is passed into your route saga.

const routes = {
  // Capture the user id with `:id` into an `id` property of the parameters
  // object that is passed into `userSaga`.
  '/users/:id': function* userSaga({ id }) {
    const user = yield call(fetchUser, id);
    yield put(setCurrentUser(user));
  },

  // You can capture multiple dynamic path parameters too.
  '/dogs/:id/friends/:friendId': function* dogSaga({ id, friendId }) {
    // ...
  },
};

If you specify a dynamic path parameter, then it will be required. This route will match /bar/42 but NOT /bar.

const routes = {
  '/bar/:id': saga,
};

Optional Named Parameters

However, you can make a path parameter optional, by ending it with ?.

This route will match /bar/42 AND /bar.

const routes = {
  '/bar/:id?': saga,
};

Using a period before an optional parameter can be optional too.

This route will match /bar/LICENSE and /bar/README.md.

const routes = {
  '/bar/:fname.:ext?': saga,
};

Wildcard

You can use * as a wildcard to match many routes.

This route would match /bar and /bar/baz/foo.

const routes = {
  '/bar/*': saga,
};

Route Precedence

Sometimes you want some routes to take precedence over others. For example, consider a /users/invite route and a /users/:id route. JavaScript objects don't guarantee order, so the /users/:id route could take precedence and match /users/invite. So, the newUser handler would never run.

// Can't guarantee precedence with an object
const routes = {
  '/users/invite': inviteUser,
  '/users/:id': newUser,
};

To fix this problem, you can define routes with an array of route objects like so.

const routes = [
  { pattern: '/users/invite', handler: inviteUser },
  { pattern: '/users/:id', handler: newUser },
];

The array form will register routes in the order you provide, ensuring precedence.

Options

As mentioned earlier, the router saga may also take a third argument, an optional options object, which allows you to specify additional behaviour as described below:

Key Description
matchAll If set to true, it allows all matching routes to run instead of the first matching route.
beforeRouteChange Set to a saga to run any time location changes. This is useful for dispatching a cleanup action before route changes.
const options = {
  matchAll: true,

  *beforeRouteChange() {
    yield put(clearNotifications());
  },
};

function* mainSaga() {
  yield fork(router, history, routes, options);
}

Navigation

Hash History

If you use hash history, then navigation will work right out of the box.

import { router, createHashHistory } from 'redux-saga-router';

const history = createHashHistory();

const routes = {
  // ...
};

function* mainSaga() {
  const data = yield call(fetchInitialData);

  yield put(ready(data));

  yield fork(router, history, routes);
}
<nav>
  <ul>
    <li><a href="#/users">Users</a></li>
    <li><a href="#/users/1">A Specific User</a></li>
  </ul>
</nav>

Browser History

Browser history depends on pushState changes, so you'll need a method for making anchor tags change history state instead of actually exhibiting their default behavior. Also, if you're building a single-page application, your server will need to support your client side routes to ensure your app loads properly.

import { router, createBrowserHistory } from 'redux-saga-router';

const history = createBrowserHistory();

// This is a naive example, so you might want something more robust
document.addEventListener('click', (e) => {
  const el = e.target;

  if (el.tagName === 'A') {
    e.preventDefault();
    history.push(el.pathname);
  }
});

const routes = {
  // ...
};

function* mainSaga() {
  // ...
}

Browser History with React

If you're using React in your application, then Redux Saga Router does export a higher-order component (HOC) that allows you to abstract away dealing with pushState manually. You can import the createLink HOC from redux-saga-router/react to create a Link component similar to what's available in React Router. Just pass in your history object to the createLink function to create the Link component. You'll probably want a separate file in your application for exporting your history object and your Link component.

If you are also using React Router, you can use the Link component that is shipped with React Router.

// history.js

import { createLink } from 'redux-saga-router/react'

// Without React Router v4:
import { createBrowserHistory } from 'redux-saga-router';

// With the history npm package:
import createBrowserHistory from 'history/createBrowserHistory';

const history = createBrowserHistory();

export const Link = createLink(history);
export { history };
// saga.js

import { router } from 'redux-saga-router';
import { history } from './history';

const routes = {
  // ...
};

function* mainSaga() {
  const data = yield call(fetchInitialData);

  yield put(ready(data));

  yield fork(router, history, routes);
}
// App.js

import React from 'react';
import { Link } from './history';

export default function App() {
  return (
    <nav>
      <ul>
        <li><Link to="/users">Users</Link></li>
        <li><Link to="/users/1">A Specific User</Link></li>
      </ul>
    </nav>
  );
}

React Router

Redux Saga Router can also work in tandem with React Router (v2, v3, and v4)! Instead of using one of Redux Saga Router's history creation functions, just use your history object from React Router (v2, v3) or use the history creation functions provided by the history npm package (v4).

// saga.js

import { router } from 'redux-saga-router';

// React Router v2 and v3:
import { browserHistory as history } from 'react-router';

// React Router v4:
import createBrowserHistory from 'history/createBrowserHistory';
const history = createBrowserHistory();

const routes = {
  // ...
};

export default function* mainSaga() {
  const data = yield call(fetchInitialData);

  yield put(ready(data));

  yield fork(router, history, routes);
}
// App.js

import React from 'react';
import { Link } from 'react-router';

export default function App({ children }) {
  return (
    <div>
      <nav>
        <ul>
          <li><Link to="/users">Users</Link></li>
          <li><Link to="/users/1">A Specific User</Link></li>
        </ul>
      </nav>

      <div>
        {children}
      </div>
    </div>
  );
}
import React from 'react';
import { render } from 'react-dom';
import { applyMiddleware, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import App from './App';
import Users from './Users';
import User from './User';
import mainSaga from './saga';

// React Router v2 and v3:
import { Router, Route, browserHistory as history } from 'react-router';

// React Router v4:
import createBrowserHistory from 'history/createBrowserHistory';
import { Router, Route } from 'react-router';
const history = createBrowserHistory();

function reducer() {
  return {};
}

const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(mainSaga);

render((
  <Router history={history}>
    <Route path="/" component={App}>
      <Route path="/users" component={Users} />
      <Route path="/users/:id" component={User} />
    </Route>
  </Router>
), document.getElementById('main'));

More Repositories

1

redux-saga-test-plan

Test Redux Saga with an easy plan.
JavaScript
1,248
star
2

revalidate

Elegant and composable validations
JavaScript
363
star
3

chroma

Ruby gem for color manipulation and palette generation
Ruby
232
star
4

run-elm

Run Elm code from the command line
JavaScript
52
star
5

marionette.component

Manage and create components for your Marionette.js application
JavaScript
36
star
6

fp-basics-in-es6

Code samples for "Functional Programming Basics in ES6" talk
JavaScript
35
star
7

redux-resource

Redux action creator for managing RESTful resources
JavaScript
32
star
8

combos

Generate all possible permutations of an object's key-value pairs
JavaScript
27
star
9

rise-of-async-js-talk

Code samples, demos, and resources for "The Rise of Async JavaScript" talk
JavaScript
26
star
10

react-bind-closures

Bind closures to stateless React components to avoid creating closures at render time
JavaScript
22
star
11

programming-elm.com

Source for programming-elm.com
Elm
19
star
12

arch-elm

Code for talk "Toward a Better Front-end Architecture: Elm"
Elm
19
star
13

fsm-iterator

A finite state machine iterator for JavaScript
JavaScript
16
star
14

website

Source for my personal website
JavaScript
15
star
15

marionette.polymerview

JavaScript
13
star
16

effective-react-testing

JavaScript
12
star
17

whitesimilarity

Ruby gem implementation of the White Similarity Algorithm
C
12
star
18

building-web-apps-with-elm-tutorial

Elm
11
star
19

perchance

A simple maybe monad for JavaScript
JavaScript
10
star
20

elm-workshop

Setup instructions and finished demos for the "Building Web Apps with Elm" workshop
9
star
21

redux-binary

Redux reducer and actions for handling binary state
JavaScript
9
star
22

react-classify

Classify functional React components to use component lifecycle hooks
JavaScript
9
star
23

elm-stream

Fast and simple stream library for Elm
Elm
8
star
24

dotfiles

Vim Script
7
star
25

redux-glue

Glue together Redux actions to create testable, sequenced actions
JavaScript
6
star
26

public-apis

Simple web app for finding public APIs
JavaScript
6
star
27

photomosaic

Web app for computing photomosaics in the browser
JavaScript
5
star
28

building-resilient-api-driven-elm

Elm
5
star
29

babel-plugin-transform-underscore-arrow-functions

Transform arrow functions with an underscore param into param-less functions
JavaScript
4
star
30

react-revalidate

Validate React component props with revalidate
JavaScript
4
star
31

elm-demos

Example Elm demo apps to complement future Elm blog posts
Elm
3
star
32

conways-game-of-life

Conway's Game of Life implemented in Elm
Elm
3
star
33

migrate-js-to-elm

JavaScript
3
star
34

meteor-rps

JavaScript
2
star
35

advanced-cypress-testing

JavaScript
2
star
36

_redux-saga-router_5

JavaScript
2
star
37

redux-revalidate

Validate your Redux store state with revalidate
JavaScript
2
star
38

measurb

Handle units of measurement in Ruby with ease!
Ruby
2
star
39

fp-in-javascript

Many common functional programming patterns succinctly implemented in JavaScript
JavaScript
2
star
40

modular-react-and-redux-talk

Code samples for the "Modular React and Redux" talk
JavaScript
2
star
41

marionette.attrsync

JavaScript
1
star
42

node-koa-es6

JavaScript
1
star
43

backbone.directattributes

Direct attributes for Backbone models
1
star
44

pat-packet-visits-ruby-rails

Ruby
1
star
45

playit

Control audio players on websites with your keyboard's Play/Pause key
JavaScript
1
star
46

state-side-effects-and-redux

Code samples for "State, Side Effects, and Redux. Oh my!" talk
JavaScript
1
star
47

es7-and-beyond-talk

JavaScript
1
star
48

bundle-with-webpack-talk

Resources for the talk "Bundle the Web with Webpack"
JavaScript
1
star
49

dynamically-sassy-demos

Ruby
1
star
50

orchestrating-apps-angular

JavaScript
1
star