{"id":14958069,"url":"https://github.com/kuy/redux-tower","last_synced_at":"2025-04-13T06:37:06.511Z","repository":{"id":14586904,"uuid":"76760362","full_name":"kuy/redux-tower","owner":"kuy","description":"Saga powered routing engine for Redux app.","archived":false,"fork":false,"pushed_at":"2022-12-06T21:01:13.000Z","size":2617,"stargazers_count":154,"open_issues_count":58,"forks_count":15,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-04-14T12:36:31.132Z","etag":null,"topics":["redux","redux-saga","router","routing","sagas","spa"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kuy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-18T03:23:05.000Z","updated_at":"2023-03-11T12:20:23.000Z","dependencies_parsed_at":"2023-01-13T18:01:28.933Z","dependency_job_id":null,"html_url":"https://github.com/kuy/redux-tower","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kuy%2Fredux-tower","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kuy%2Fredux-tower/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kuy%2Fredux-tower/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kuy%2Fredux-tower/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kuy","download_url":"https://codeload.github.com/kuy/redux-tower/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248675334,"owners_count":21143763,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["redux","redux-saga","router","routing","sagas","spa"],"created_at":"2024-09-24T13:16:09.120Z","updated_at":"2025-04-13T06:37:06.491Z","avatar_url":"https://github.com/kuy.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![NPM Package][npm_img]][npm_site]\n[![Travis][ci_img]][ci_site]\n[![Coverage Status][ca_img]][ca_site]\n[![Dependency Status][david_img]][david_site]\n[![Greenkeeper badge](https://badges.greenkeeper.io/kuy/redux-tower.svg)](https://greenkeeper.io/)\n\n# redux-tower\n\n[Saga](https://github.com/redux-saga/redux-saga) powered routing engine for [Redux](http://redux.js.org/) apps.\n\nredux-tower provides a way to fully control client-side routing with its related side effects\nsuch as data fetching, user authentication, fancy animations.\n\n**NOTICE: This package is ACTIVELY under development. API (both public and internal) may change suddenly.**\n\n\n## Installation\n\n```\nnpm install --save redux-tower\n```\n\n\n## The Goal\n\n+ Integrated, Battery-included, but Replaceable\n+ Affinity with Redux\n\n\n## Why?\n\n+ [react-router](https://github.com/ReactTraining/react-router) is just a component switcher. I don't want to depend on React component lifecycle.\n+ [react-router-redux](https://github.com/reactjs/react-router-redux) doesn't help you to do something before showing a page component.\n+ [redux-saga](https://github.com/redux-saga/redux-saga) brings long-running processes with async control flow to Redux.\n\n### About redux-saga-router\n\n[redux-saga-router](https://github.com/jfairbank/redux-saga-router) is a great routing library,\nwhich brings sagas to the chaotic router world and gives a way to do side effects in redux-saga way when associated url is activated.\nHowever, it can't be used to control the timing of showing the page component and what component should be shown,\nbecause both react-router and redux-saga-router are working separately. I feel it annoying to maintain the separated route definitions.\n\n\n## Examples\n\n### Online Demo\n\n+ **Minimum**: [Source](https://github.com/kuy/redux-tower/tree/master/examples/minimum) | [Demo](http://kuy.github.io/redux-tower/minimum/) | Minimum usage example with Hash based history.\n+ **Blog**: [Source](https://github.com/kuy/redux-tower/tree/master/examples/blog) | [Demo](http://kuy.github.io/redux-tower/blog/) | Real World Blog app using [Semantic UI React](https://github.com/Semantic-Org/Semantic-UI-React).\n\n[redux-logger](https://github.com/evgenyrodionov/redux-logger) is enabled. Open the JavaScript console of developer tools in your browser.\nYou can also use [Redux DevTools extension](https://github.com/zalmoxisus/redux-devtools-extension) to see the store and the actions being fired.\n\n### Try in local\n\nClone this repository and run following npm commands.\n\n```\nnpm install\nnpm start\n```\n\nAnd then open `http://localhost:8080/` with your favorite browser.\n\n\n## Usage\n\nHere is a SFA (Single File Application) that shows you a simple routing with side effects.\n\n```js\n// Pages\nfunction Navigation() {\n  return \u003cul\u003e\n    \u003cli\u003e\u003ca href='#/'\u003eIndex\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href='#/tower'\u003eTower\u003c/a\u003e\u003c/li\u003e\n  \u003c/ul\u003e;\n}\n\nclass Index extends Component {\n  render() {\n    return \u003cdiv\u003e\n      \u003ch1\u003eIndex\u003c/h1\u003e\n      \u003cNavigation /\u003e\n      \u003cp\u003eHi, here is index page.\u003c/p\u003e\n    \u003c/div\u003e;\n  }\n}\n\nclass Tower extends Component {\n  render() {\n    return \u003cdiv\u003e\n      \u003ch1\u003eTower\u003c/h1\u003e\n      \u003cNavigation /\u003e\n      \u003cp\u003eHere is tower page. You waited a while for loading this page.\u003c/p\u003e\n    \u003c/div\u003e;\n  }\n}\n\n// Routes\nconst routes = {\n  '/': Index,\n  *'/tower'() {\n    yield call(delay, 1000);\n    yield Tower;\n  }\n};\n\n// History\nconst history = createHashHistory();\n\n// Saga\nfunction* rootSaga() {\n  yield fork(routerSaga, { history, routes });\n}\n\n// Reducer\nconst reducer = combineReducers(\n  { router: routerReducer }\n);\n\nconst sagaMiddleware = createSagaMiddleware();\nconst store = createStore(reducer, {}, applyMiddleware(\n  sagaMiddleware, logger()\n));\nsagaMiddleware.run(rootSaga);\n\nReactDOM.render(\n  \u003cProvider store={store}\u003e\n    \u003cRouter /\u003e\n  \u003c/Provider\u003e,\ndocument.getElementById('container'));\n```\n\n\n## API / Building Blocks\n\nredux-tower consists of several different kinds of elements/components.\nIn this section, I'd like to introduce them step by step and how to integrate with your Redux application.\n\n### Routes\n\nFirst of all, you need to have the route definition which contains URL patterns and route actions.\nThe behavior of routing is deadly simple. When a url pattern is activated, the engine tests URL patterns,\nand pick a route action from your definition, and runs it.\nThe difference with other routing libraries is that this is not a simple component switcher like react-router.\nYou 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.\nFor increasing readability and productivity, redux-tower allows you to use a shorthand notation for changing components and redirections.\nThe 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.\n\n```js\nimport { actions, INITIAL, CANCEL, ERROR } from 'redux-tower';\nimport Home from '../path/to/home';\n\nconst routes = {\n  // Initial action or component (Optional)\n  [INITIAL]: Loading,\n\n  '/': function* homePage() {\n    // Do something, such as data fetching, authentication, etc.\n    yield call(fetch, ...);\n\n    // Update Redux's state\n    yield put(data(...));\n\n    // Change component\n    yield Home; // Shorthand\n  },\n\n  // Nested routes\n  '/posts': {\n    // Receive query string like '/posts?q=keyword'\n    // Use method syntax for route action\n    *'/'({ query }) {\n      yield call(loadPosts, query);\n\n      // Change component (not shorthand)\n      yield put(actions.changeComponent(PostsIndex));\n    },\n\n    // Receive named parameters like '/posts/1'\n    '/:id': function* postsShowPage({ params: { id } }) {\n      yield call(loadPost, id);\n      yield PostsShow;\n    },\n\n    // Redirect to '/posts' after saving\n    '/:id/update': function* postsUpdateAction({ params: { id } }) {\n      yield call(savePost, ...);\n      yield '/posts'; // Shorthand\n    }\n  },\n\n  // Redirect to '/posts/:id' with fixed parameter\n  '/about': '/posts/2', // Shorthand (lazy redirection)\n\n  // Change component\n  // Assign React component directly (except Stateless Functional Components)\n  '/contact': Contact,\n\n  // Default error page (Optional)\n  [ERROR]: NotFound,\n\n  // Global cancel action (Optional)\n  [CANCEL]: function* cancel() {\n    yield call(cancelFetch);\n  }\n};\n```\n\n### Hooks\n\nIn the route definition, a route action can have the entering/leaving hooks that are ran before/after the main action.\nIt's a bit tricky behavior because the both hooks have a different timing when they are executed.\n\n```js\nconst routes = {\n  // ...\n\n  // Enable entering hook\n  '/admin': [function* enterAdmin() {\n    // Check logged-in or not\n    if (yield select(isNotLoggedIn)) {\n      // Redirect to login page\n      yield '/users/login';\n    }\n  }, {\n    // Admin section\n    '/': './dashboard',\n    '/dashboard': AdminDashboard,\n    '/posts': {\n      // Enable leaving hook\n      '/:id/edit': [AdminPostsEdit, function* leaveEdit() {\n        // Dirty check\n        if (yield select(isDirty)) {\n          // Prevent page transition\n          yield false;\n        }\n      }]\n    }\n  }]\n\n  '/users': {\n    '/login': UsersLogin,\n    '/logout': function* logout() {\n      yield call(logout);\n      yield '/';\n    },\n  }\n};\n```\n\n### History\n\nredux-tower is built on [history](https://www.npmjs.com/package/history) package so that you can choose a strategy from Hash based or History API.\n\n```js\n// History API\nimport { createBrowserHistory as createHistory } from 'redux-tower';\n\n// Or Hash based\nimport { createHashHistory as createHistory } from 'redux-tower';\n\n// ...\n\nconst history = createHistory();\n```\n\n### Saga\n\nThe core of routing engine, which mainly have two respnsibilities:\n\n+ Detects location changes from `history` instance, reflects location data to Redux's store, and triggers route actions\n+ Watches history related Redux's actions and operates `history` instance\n\nSince 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.\nDon't forget to pass the option when you fork. Here is a list of options.\n\n+ history: An instance of `createBrowserHistory()` or `createHashHistory()`.\n+ routes: A route definition that previously introduced.\n* offset: [Optional] A offset path for `createBrowserHistory()`. No need to use for `createHashHistory()`.\n\n```js\nimport { saga as router } from 'redux-tower';\n\n// ...\n\nexport default function rootSaga() {\n  yield fork(router, { history, routes });\n\n  // ...\n}\n```\n\n### Reducer\n\nA reducer is used to expose the location data to Redux's store.\n\n+ path: String. Path string, which is stripped a query string.\n+ params: Object. Named parameters, which is mapped with placeholders in route patterns. `/users/:id` with `/users/1` gets `{ id: '1' }`.\n+ query: Object. Parsed query string. `/search?q=hoge` gets `{ q: 'hoge' }`.\n+ splats: Array. Unnamed parameters, which is splatted from placeholders in route patterns. `/posts/*/*`.\n\n```js\nimport { reducer as router } from 'redux-tower';\n\n// ...\n\nexport default combineReducers(\n  { /* your reducers */, router }\n);\n```\n\n### Actions\n\nSince this library is made for Redux, all state transitions, including routing, are triggered by actions.\n\n#### Core actions\n\n+ `CHANGE_COMPONENT`: switch to other component\n\n#### History actions\n\n+ `PUSH`: pushes a new path\n+ `REPLACE`: repalces with a new path\n+ `GO`: \n+ `GO_BACK`: \n+ `GO_FORWARD`: \n\n### React components\n\nThese React components will help you for building an application.\nI'm happy to hear feature requests and merge your PRs if you feel it doesn't satisfy your needs.\n\n#### `\u003cRouter\u003e`\n\nA simple component switcher, which is connected with Redux.\n\n```js\nimport { Router } from 'redux-tower/lib/react';\n\n// ...\n\nReactDOM.render(\n  \u003cProvider store={configureStore()}\u003e\n    \u003cRouter /\u003e\n  \u003c/Provider\u003e,\ndocument.getElementById('container'));\n```\n\n#### `\u003cLink\u003e`\n\n`\u003cLink\u003e` component helps you to put a link in your Redux application.\n\n```js\nimport { Link } from 'redux-tower/lib/react';\n\n// ...\n\nclass Page extends Component {\n  render() {\n    return \u003cdiv\u003e\n      \u003cLink to='/'\u003eHome\u003c/Link\u003e\n      \u003cLink external to='https://github.com/kuy'\u003e@kuy\u003c/Link\u003e\n    \u003c/div\u003e;\n  }\n}\n```\n\n\n## Acknowledgment\n\nredux-tower has inspired by [redux-saga-router](https://github.com/jfairbank/redux-saga-router).\nBig thanks to [@jfairbank](https://github.com/jfairbank).\n\n\n## License\n\nMIT\n\n\n## Author\n\nYuki Kodama / [@kuy](https://twitter.com/kuy)\n\n\n[npm_img]: https://img.shields.io/npm/v/redux-tower.svg\n[npm_site]: https://www.npmjs.org/package/redux-tower\n[ci_img]: https://img.shields.io/travis/kuy/redux-tower/master.svg?style=flat-square\n[ci_site]: https://travis-ci.org/kuy/redux-tower\n[ca_img]: https://coveralls.io/repos/github/kuy/redux-tower/badge.svg?branch=master\n[ca_site]: https://coveralls.io/github/kuy/redux-tower?branch=master\n[david_img]: https://img.shields.io/david/kuy/redux-tower.svg\n[david_site]: https://david-dm.org/kuy/redux-tower\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkuy%2Fredux-tower","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkuy%2Fredux-tower","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkuy%2Fredux-tower/lists"}