• Stars
    star
    154
  • Rank 242,095 (Top 5 %)
  • Language
    JavaScript
  • Created almost 8 years ago
  • Updated almost 2 years ago

Reviews

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

Repository Details

Saga powered routing engine for Redux app.

NPM Package Travis Coverage Status Dependency Status Greenkeeper badge

redux-tower

Saga powered routing engine for Redux apps.

redux-tower provides a way to fully control client-side routing with its related side effects such as data fetching, user authentication, fancy animations.

NOTICE: This package is ACTIVELY under development. API (both public and internal) may change suddenly.

Installation

npm install --save redux-tower

The Goal

  • Integrated, Battery-included, but Replaceable
  • Affinity with Redux

Why?

  • react-router is just a component switcher. I don't want to depend on React component lifecycle.
  • react-router-redux doesn't help you to do something before showing a page component.
  • redux-saga brings long-running processes with async control flow to Redux.

About redux-saga-router

redux-saga-router is a great routing library, which brings sagas to the chaotic router world and gives a way to do side effects in redux-saga way when associated url is activated. However, it can't be used to control the timing of showing the page component and what component should be shown, because both react-router and redux-saga-router are working separately. I feel it annoying to maintain the separated route definitions.

Examples

Online Demo

redux-logger is enabled. Open the JavaScript console of developer tools in your browser. You can also use Redux DevTools extension to see the store and the actions being fired.

Try in local

Clone this repository and run following npm commands.

npm install
npm start

And then open http://localhost:8080/ with your favorite browser.

Usage

Here is a SFA (Single File Application) that shows you a simple routing with side effects.

// Pages
function Navigation() {
  return <ul>
    <li><a href='#/'>Index</a></li>
    <li><a href='#/tower'>Tower</a></li>
  </ul>;
}

class Index extends Component {
  render() {
    return <div>
      <h1>Index</h1>
      <Navigation />
      <p>Hi, here is index page.</p>
    </div>;
  }
}

class Tower extends Component {
  render() {
    return <div>
      <h1>Tower</h1>
      <Navigation />
      <p>Here is tower page. You waited a while for loading this page.</p>
    </div>;
  }
}

// Routes
const routes = {
  '/': Index,
  *'/tower'() {
    yield call(delay, 1000);
    yield Tower;
  }
};

// History
const history = createHashHistory();

// Saga
function* rootSaga() {
  yield fork(routerSaga, { history, routes });
}

// Reducer
const reducer = combineReducers(
  { router: routerReducer }
);

const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, {}, applyMiddleware(
  sagaMiddleware, logger()
));
sagaMiddleware.run(rootSaga);

ReactDOM.render(
  <Provider store={store}>
    <Router />
  </Provider>,
document.getElementById('container'));

API / Building Blocks

redux-tower consists of several different kinds of elements/components. In this section, I'd like to introduce them step by step and how to integrate with your Redux application.

Routes

First of all, you need to have the route definition which contains URL patterns and route actions. The behavior of routing is deadly simple. When a url pattern is activated, the engine tests URL patterns, and pick a route action from your definition, and runs it. The difference with other routing libraries is that this is not a simple component switcher like react-router. You can write a route action includes async control flows and interactions with Redux naturally to fully control the process of routing thanks to redux-saga. For increasing readability and productivity, redux-tower allows you to use a shorthand notation for changing components and redirections. The URL pattern is a plain string, but is able to capture a part of URL and captured values are passed to a route action as named parameters.

import { actions, INITIAL, CANCEL, ERROR } from 'redux-tower';
import Home from '../path/to/home';

const routes = {
  // Initial action or component (Optional)
  [INITIAL]: Loading,

  '/': function* homePage() {
    // Do something, such as data fetching, authentication, etc.
    yield call(fetch, ...);

    // Update Redux's state
    yield put(data(...));

    // Change component
    yield Home; // Shorthand
  },

  // Nested routes
  '/posts': {
    // Receive query string like '/posts?q=keyword'
    // Use method syntax for route action
    *'/'({ query }) {
      yield call(loadPosts, query);

      // Change component (not shorthand)
      yield put(actions.changeComponent(PostsIndex));
    },

    // Receive named parameters like '/posts/1'
    '/:id': function* postsShowPage({ params: { id } }) {
      yield call(loadPost, id);
      yield PostsShow;
    },

    // Redirect to '/posts' after saving
    '/:id/update': function* postsUpdateAction({ params: { id } }) {
      yield call(savePost, ...);
      yield '/posts'; // Shorthand
    }
  },

  // Redirect to '/posts/:id' with fixed parameter
  '/about': '/posts/2', // Shorthand (lazy redirection)

  // Change component
  // Assign React component directly (except Stateless Functional Components)
  '/contact': Contact,

  // Default error page (Optional)
  [ERROR]: NotFound,

  // Global cancel action (Optional)
  [CANCEL]: function* cancel() {
    yield call(cancelFetch);
  }
};

Hooks

In the route definition, a route action can have the entering/leaving hooks that are ran before/after the main action. It's a bit tricky behavior because the both hooks have a different timing when they are executed.

const routes = {
  // ...

  // Enable entering hook
  '/admin': [function* enterAdmin() {
    // Check logged-in or not
    if (yield select(isNotLoggedIn)) {
      // Redirect to login page
      yield '/users/login';
    }
  }, {
    // Admin section
    '/': './dashboard',
    '/dashboard': AdminDashboard,
    '/posts': {
      // Enable leaving hook
      '/:id/edit': [AdminPostsEdit, function* leaveEdit() {
        // Dirty check
        if (yield select(isDirty)) {
          // Prevent page transition
          yield false;
        }
      }]
    }
  }]

  '/users': {
    '/login': UsersLogin,
    '/logout': function* logout() {
      yield call(logout);
      yield '/';
    },
  }
};

History

redux-tower is built on history package so that you can choose a strategy from Hash based or History API.

// History API
import { createBrowserHistory as createHistory } from 'redux-tower';

// Or Hash based
import { createHashHistory as createHistory } from 'redux-tower';

// ...

const history = createHistory();

Saga

The core of routing engine, which mainly have two respnsibilities:

  • Detects location changes from history instance, reflects location data to Redux's store, and triggers route actions
  • Watches history related Redux's actions and operates history instance

Since it's provided as a saga, what you have to do is just launching it using fork effect in the root saga of your application. Don't forget to pass the option when you fork. Here is a list of options.

  • history: An instance of createBrowserHistory() or createHashHistory().
  • routes: A route definition that previously introduced.
  • offset: [Optional] A offset path for createBrowserHistory(). No need to use for createHashHistory().
import { saga as router } from 'redux-tower';

// ...

export default function rootSaga() {
  yield fork(router, { history, routes });

  // ...
}

Reducer

A reducer is used to expose the location data to Redux's store.

  • path: String. Path string, which is stripped a query string.
  • params: Object. Named parameters, which is mapped with placeholders in route patterns. /users/:id with /users/1 gets { id: '1' }.
  • query: Object. Parsed query string. /search?q=hoge gets { q: 'hoge' }.
  • splats: Array. Unnamed parameters, which is splatted from placeholders in route patterns. /posts/*/*.
import { reducer as router } from 'redux-tower';

// ...

export default combineReducers(
  { /* your reducers */, router }
);

Actions

Since this library is made for Redux, all state transitions, including routing, are triggered by actions.

Core actions

  • CHANGE_COMPONENT: switch to other component

History actions

  • PUSH: pushes a new path
  • REPLACE: repalces with a new path
  • GO:
  • GO_BACK:
  • GO_FORWARD:

React components

These React components will help you for building an application. I'm happy to hear feature requests and merge your PRs if you feel it doesn't satisfy your needs.

<Router>

A simple component switcher, which is connected with Redux.

import { Router } from 'redux-tower/lib/react';

// ...

ReactDOM.render(
  <Provider store={configureStore()}>
    <Router />
  </Provider>,
document.getElementById('container'));

<Link>

<Link> component helps you to put a link in your Redux application.

import { Link } from 'redux-tower/lib/react';

// ...

class Page extends Component {
  render() {
    return <div>
      <Link to='/'>Home</Link>
      <Link external to='https://github.com/kuy'>@kuy</Link>
    </div>;
  }
}

Acknowledgment

redux-tower has inspired by redux-saga-router. Big thanks to @jfairbank.

License

MIT

Author

Yuki Kodama / @kuy

More Repositories

1

redux-saga-examples

Yet another example repository for redux-saga.
JavaScript
404
star
2

redux-saga-chat-example

A chat app built with redux-saga and Socket.IO.
JavaScript
288
star
3

redux-tooltip

A tooltip React component for Redux.
JavaScript
130
star
4

treemap-with-router

An example for react-router-redux with d3's treemap.
JavaScript
63
star
5

reason-of-life

Conway's Lifegame in Reason + reason-react with webpack + bs-loader.
OCaml
27
star
6

slack-loc

Update Slack status based on your location (using Wi-Fi SSID).
Shell
26
star
7

lifegame-redux

Conway's lifegame using Redux + various middlewares.
JavaScript
21
star
8

redux-merge-reducers

A decorator to make your Redux's reducers mergeable.
JavaScript
19
star
9

oberis

Tetris + Obelisk.js with Redux.
JavaScript
15
star
10

popover-profile

A small app to demonstrate shared self-contained components.
JavaScript
13
star
11

react-transport

Transports your React component to the outside of React rendering tree.
JavaScript
11
star
12

decom

Decompose docker-compose logs and organize them.
Rust
8
star
13

babel-plugin-transform-immutablejs

Transform built-in collection operations to Immutable.js ones.
JavaScript
8
star
14

jsonbox-rs

Rust wrapper for jsonbox.io
Rust
8
star
15

testdouble-timers

Fake timers API for testdouble.js.
JavaScript
8
star
16

avn-nodebrew

avn plugin for nodebrew
JavaScript
7
star
17

macro-harness

Test harness for procedural macros
Rust
7
star
18

nada.re

Visualization of Abelian sandpile model written in Reason + Obelisk.js.
OCaml
6
star
19

authist

Automatic 2FA/MFA assistant with OCR your smartphone.
JavaScript
6
star
20

redux-autocomplete

A React component react-autocomplete for Redux.
JavaScript
5
star
21

elevato-rs

An Elevator Simulator written in Rust with Amethyst game engine.
Rust
5
star
22

gmeet-slash-cmd

Slack integration for Google Meet
TypeScript
5
star
23

cizen-chat

Elixir
5
star
24

td4-js

An emulator of TD4 (4-bit tiny CPU) written in JS+Flowtype+Redux.
JavaScript
5
star
25

sc-repeat-playlist

A Chrome Extension providing "Repeat Playlist" feature to SoundCloud.
JavaScript
4
star
26

redux-saga-takex

A powerful take effect accepting RegExp instead of listing action types.
JavaScript
4
star
27

aia

AIA: Illustrator Automated
JavaScript
3
star
28

domain-specific-saga

A helper library to realize Domain Specific Saga for your Redux apps.
JavaScript
3
star
29

electron-connect-webpack-plugin

electron-connect integration for webpack.
JavaScript
3
star
30

gkkj-web

srouce code for Geek House Gokokuji (http://gkkj.org)
JavaScript
3
star
31

warp-example-app

Example web app written in Rust with warp + diesel + tera.
Rust
3
star
32

redux-middleware-logger

Logger for Redux's middleware.
JavaScript
3
star
33

react-cropview

A React component providing a cropped view for large contents and making it draggable.
JavaScript
3
star
34

asdf-pixel-sort

Rust implementation of pixel sorting algorithm "ASDF" by Kim Asendorf
Rust
3
star
35

console.ws

WebSocket powered remote console.log() for Node.js
JavaScript
2
star
36

wistant-fb-toc

TypeScript
2
star
37

docker-nginx-upstream-dynamic

Alpine based nginx container with nginx-upstream-dynamic-servers module.
Dockerfile
2
star
38

dining-philosophers

Dining Philosophers Problem in Rust.
Rust
2
star
39

funl

peco/percol powered interactive placeholders in shell environment.
Shell
2
star
40

sqlx-pool-study

Rust
2
star
41

capistrano-nodebrew

nodebrew support for Capistrano 3.x
Ruby
2
star
42

actix-delay

Simulates a delayed response for actix-web
Rust
2
star
43

monkey-lang

OCaml
2
star
44

blog

HTML
1
star
45

asdf-study

Study of ASDF Pixel Sort
Processing
1
star
46

mockall-study

Rust
1
star
47

menthas-cordova

Menthas for Android/iOS using Apache Cordova.
JavaScript
1
star
48

rustracer

A toy path tracer written in Rust.
Rust
1
star
49

perceptron

OCaml
1
star
50

decent-profile-viewer-seed

Rust
1
star
51

today-anime

I can't keep in mind a day of the week of my favorite animes' broadcast πŸ˜‡
OCaml
1
star
52

axum-study

Rust
1
star
53

kiyoshi

Zun Doko Kiyoshi!
JavaScript
1
star
54

decent-profile-viewer

Rust
1
star
55

saml-proxy

SAML proxy built with Node.js and run on AWS ECS/Fargate.
JavaScript
1
star
56

acot-reporter-null

A null reporter for @acot/cli.
TypeScript
1
star
57

parseco

Learning parser combinators in OCaml + Core.
OCaml
1
star
58

otel-study

Rust
1
star
59

opendatastructures-study

Rust
1
star
60

relit-lifegame

OCaml
1
star
61

jsonbox-todo-example

Usage example of jsonbox.rs
Rust
1
star
62

conscale

Rust
1
star
63

kuy.github.io

HTML
1
star
64

acot-reporter-github

A GitHub reporter for acot
JavaScript
1
star
65

peddyo

Slack bot written in OCaml
OCaml
1
star
66

rust-hello

Rust
1
star
67

rust-cross-into-study

Rust
1
star
68

lazy-spinner

A small application to demonstrate the lazy loading spinner using React + Redux.
JavaScript
1
star
69

dining-philosophers-aa

Rust's async/await version of Dining Philosophers Problem.
Rust
1
star
70

flix-sandbox

1
star
71

winit-study

Rust
1
star
72

client-dynamodb-study

DynamoDB of AWS JavaScript SDK v3
JavaScript
1
star
73

camera-proc

Rust
1
star
74

cizen-cells

Time-driven two-dimensional asynchronous cellular automaton with Moore neighborhood.
Elixir
1
star
75

acot-reporter-slack

A Slack reporter for acot.
TypeScript
1
star
76

lifegame.rs

Conway's lifegame written in Rust.
Rust
1
star