{"id":21840268,"url":"https://github.com/zapier/redux-router-kit","last_synced_at":"2025-04-14T10:51:25.423Z","repository":{"id":57141935,"uuid":"55075929","full_name":"zapier/redux-router-kit","owner":"zapier","description":"Routing tools for React+Redux","archived":false,"fork":false,"pushed_at":"2020-08-28T02:56:21.000Z","size":717,"stargazers_count":50,"open_issues_count":11,"forks_count":6,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-03-28T00:04:51.536Z","etag":null,"topics":["react","redux","router"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zapier.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-30T15:45:13.000Z","updated_at":"2024-11-12T19:10:22.000Z","dependencies_parsed_at":"2022-09-05T18:50:55.817Z","dependency_job_id":null,"html_url":"https://github.com/zapier/redux-router-kit","commit_stats":null,"previous_names":[],"tags_count":62,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zapier%2Fredux-router-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zapier%2Fredux-router-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zapier%2Fredux-router-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zapier%2Fredux-router-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zapier","download_url":"https://codeload.github.com/zapier/redux-router-kit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248650420,"owners_count":21139672,"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":["react","redux","router"],"created_at":"2024-11-27T21:25:27.381Z","updated_at":"2025-04-14T10:51:25.403Z","avatar_url":"https://github.com/zapier.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redux Router Kit\n\n[![travis](https://travis-ci.org/zapier/redux-router-kit.svg?branch=master)](https://travis-ci.org/zapier/redux-router-kit)\n\nRedux Router Kit is a routing solution for React that leverages Redux to store routing and transition states and enable powerful middleware and `connect`-powered components.\n\n**Version 1.0.0 _requires_ React 16+**\n\n## Features\n\n- Routing state lives in the store just like any other state.\n- Redux middleware has full access to routing and transition state along with your other store state.\n- The built-in onLeave/onEnter hooks have access to store state and `dispatch`. onLeave/onEnter hooks can easily see the current and next routing state.\n- Use `connect` to grab routing state anywhere in your component tree, just like other Redux store state.\n- Because transition states live in the store, you can render transition states or otherwise react to transition states in your React components.\n- Fetching routes asynchronously also exposes state in the store.\n- Even though routes can be fetched asynchronously, you can still easily work with the currently available routes synchronously.\n- Fall-through to server rendering for unmatched routes.\n- Ability to force server-rendering when needed.\n\n## Why not React Router?\n\nIf the features above aren't useful, by all means, use React Router instead! It's battle-tested, and Redux Router Kit borrows its concepts heavily! Redux Router Kit is an alternative that gives tighter integration with Redux.\n\n## Is this thing ready?\n\nWell, it's been used in production for https://zapier.com for a while now. So for us, it's ready. :-) For your use case, you may find edges. If so, let us know!\n\n## Install\n\n```bash\nnpm install redux-router-kit --save\n```\n\n## Basic usage\n\n```js\nimport ReactDOM from 'react-dom';\nimport { combineReducers, applyMiddleware, Provider } from 'react-redux';\nimport {\n  routerReducer, createRouterMiddleware, RouterHistoryContainer\n} from 'redux-router-kit';\n\nconst HomePage = () =\u003e (\n  \u003cdiv\u003eHome!\u003c/div\u003e\n);\n\nconst TodoApp = ({params}) =\u003e (\n  params.id ? (\n    \u003cdiv\u003eTodo: {params.id}\u003c/div\u003e\n  ) : (\n    \u003cdiv\u003eTodos: {/* list todos */}\u003c/div\u003e\n  )\n);\n\n// You can point route paths directly to components in simple cases.\nconst routes = {\n  '/': HomePage,\n  '/todos': TodoApp,\n  '/todos/:id': TodoApp\n};\n\nconst reducer = combineReducers({\n  router: routerReducer\n});\n\nconst store = createStore(\n  reducer,\n  applyMiddleware(\n    createRouterMiddleware({routes})\n  )\n);\n\nconst Root = createReactClass({\n  render() {\n    return (\n      \u003cRouterHistoryContainer routes={routes}/\u003e\n    )\n  }\n})\n\nReactDOM.render(\n  \u003cProvider\u003e\n    \u003cRoot/\u003e\n  \u003c/Provider\u003e\n  document.getElementById('app')\n);\n```\n\n## Nested routes, onLeave/onEnter, and assign\n\n```js\nconst Layout = ({children}) =\u003e (\n  \u003cdiv\u003e\n    \u003cheader\u003eThe Header\u003c/header\u003e\n    \u003cdiv\u003e{children}\u003c/div\u003e\n  \u003c/div\u003e\n);\n\nconst HomePage = () =\u003e (\n  \u003cdiv\u003eHome!\u003c/div\u003e\n);\n\nconst TodoApp = ({children}) =\u003e (\n  \u003cdiv\u003e{children}\u003c/div\u003e\n);\n\nconst TodoList = () =\u003e (\n  \u003cdiv\u003eTodos: {/* list todos */}\u003c/div\u003e\n);\n\nconst TodoItem = ({params}) =\u003e (\n  \u003cdiv\u003eTodo: {params.id}\u003c/div\u003e\n);\n\nconst routes = {\n  '/': {\n    component: Layout,\n    routes: {\n      // This is the \"index\" route. (Like a current directory.)\n      '.': {\n        component: HomePage\n      },\n      '/todos': {\n        component: TodoApp,\n        routes: {\n          '.': {\n            component: TodoList,\n            assign({query}) {\n              if (query.page) {\n                // Return any properties for `routing.next`/`routing.current`.\n                // These can be new ad-hoc properties, or modifications of\n                // params or query.\n                return {\n                  query: {\n                    ...query,\n                    // Convert page to an integer.\n                    page: parseInt(query.page)\n                  }\n                };\n              }\n            }\n          },\n          'new': {\n            onEnter({routeTo}) {\n              // This might be a terrible example, if this is slow.\n              return createTodo()\n                .then(todo =\u003e {\n                  routeTo(`/todos/${todo.id}`);\n                })\n            }\n          },\n          ':id': {\n            component: TodoItem,\n            onLeave({router, cancelRoute, getState, dispatch}) {\n              const todo = getState().todos[router.current.params.id];\n              if (!todo.isSaved) {\n                cancelRoute();\n                dispatch(notifyToSaveTodo());\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n};\n```\n\n## Router / RouterContainer / RouterHistoryContainer\n\nThe Router component requires a `routing` prop with routing state from the store.\n\nThe RouterContainer component is connected to the store, and so automatically gets that prop.\n\nThe RouterHistoryContainer component adds in a History component to update browser address state and automatically dispatch routing actions when the browser history changes.\n\nAll these components accept the following props.\n\n### `routes`\n\nRoute mapping object. See the examples above.\n\n### `renderBeforeCurrent({router})`\n\nIf there is no current route, this function will be called.\n\n### `render({router, query, params, matchedRoutes})`\n\nIf you'd like to take control of all rendering for routes, pass in this function. No other rendering functions will be called. If no routes match, then `matchedRoutes` will be `null`.\n\n###  `renderRoutes({router, query, params, matchedRoutes})`\n\nLike `render`, but only called if there are matchedRoutes.\n\n### `renderDefault({router, query, params, matchedRoutes})`\n\nIf the matching routes don't have any components or don't reduce to a single element, this function will be called.\n\n### `renderRoot({router, query, params, matchedRoutes})`\n\nAfter all components have reduced to a single element (or map of named elements), this function will be called to render any wrapping elements.\n\n### `createElement(Component, {router, query, params, matchedRoutes, route, children})`\n\nFor each component in a route, this function is called to return an element to be rendered. If child routes provide named components, named elements will be passed as props instead of `children`.\n\n## Routing component props\n\nComponents rendered by routes receive the following props. These will also be passed to `createElement` if you provide that function to `Router`/`RouterContainer`/`RouterHistoryContainer`. (As well as the other render callbacks listed above.)\n\n### `router`\n\nThis is the current routing state. An example of the routing state is:\n\n```js\n{\n  // When the url changes, `current` moves to `previous`.\n  previous: {\n    url: '/todos/123',\n    // ... same properties as current\n  },\n  current: {\n    url: '/todos/123?edit=true',\n    query: {\n      edit: 'true'\n    },\n    params: {\n      id: 123\n    },\n    routeKey: ['/todos', ':id'],\n    location: {\n      host: 'www.example.com',\n      pathname: '/todos/123',\n      protocol: 'https:',\n      // etc., just like browser's location\n    },\n    replace: false,\n    state: null\n  },\n  // When the url changes, `next` will first get the new value of `current`.\n  // Middleware or components can then cancel or redirect. If not canceled\n  // or redirected, `current` will then become `next`. If `next` is null,\n  // there is no current transition.\n  next: null\n}\n```\n\n### `matchedRoutes`\n\nAn array of matched routes.\n\n### `route`\n\nThe specific route being rendered.\n\n### `params`\n\nThe route parameters.\n\n### `query`\n\nThe query parameters.\n\n## Links\n\nWhen you use `RouterHistoryContainer`, it responds to click/touch events so routing actions are automatically triggered. So you don't have to use a special `\u003cLink\u003e` component. A normal `\u003ca\u003e` will work just fine.\n\n## Routing action creators\n\n### `routeTo(url, {event, replace, exit})`\n\nReturns a `ROUTE_TO_NEXT` action, which, when dispatched, adds `url` to `router.next` state. Calls `onLeave` hooks for any routes which are removed and `onEnter` hooks for any routes which are added.\n\nThe route can be canceled with `cancelRoute` or redirected or exited with another `routeTo`.\n\nIf `event` is provided, it will be inspected for things like command-click to open new tabs.\n\nIf `exit` is provided, the route will roughly be equivalent to:\n\n```js\nwindow.location.href = url\n```\n\n(Currently, only absolute urls are supported though.)\n\n### `cancelRoute()`\n\nCancels the `router.next` route and removes it from state.\n\n## Dispatching routing actions from components\n\nIf you do want to manually trigger routing actions, you can either manually wire up the action with `connect`:\n\n```js\nimport { routeTo } from 'redux-router-kit';\n\nconst AddTodoButton = ({routeTo}) =\u003e (\n  \u003cbutton onClick={() =\u003e routeTo('/todos/new')}\u003eAdd New Todo\u003c/button\u003e\n);\n\nconst ConnectedAddTodoButton = connect(\n  null,\n  (dispatch) =\u003e {\n    routeTo(...args) {\n      dispatch(routeTo(...args));\n    }\n  }\n)(AddTodoButton);\n```\n\nOr you can use the included `connectRouterActions` to add the actions as props.\n\n```js\nimport { connectRouterActions } from 'redux-router-kit';\n\nconst AddTodoButton = ({routeTo}) =\u003e (\n  \u003cbutton onClick={() =\u003e routeTo('/todos/new')}\u003eAdd New Todo\u003c/button\u003e\n);\n\nconst ConnectedAddTodoButton = connectRouterActions(AddTodoButton);\n```\n\nIf you only need `routeTo` (because you typically don't need `cancelRoute`), then you can use `connectRouteTo` instead.\n\n## Connecting your components to routing state\n\nYou can use `connect` to grab any routing state for your components. For example:\n\n```js\nconst TodoItem = ({query, todo}) =\u003e {\n  const style = query.theme === 'dark' ? {\n    color: 'white',\n    backgroundColor: 'black'\n  } : {};\n  return \u003cdiv style={style}\u003e{todo.title}\u003c/div\u003e;\n};\n\nconst TodoItemContainer = connect(\n  state =\u003e ({\n    query: state.router.current.query\n  })\n)(TodoItem);\n```\n\nYou can also use `connectRouter` to grab _all_ routing state and action creators for your components. For example:\n\n```js\nconst TodoItem = ({router, todo}) =\u003e {\n  const style = router.current.query.theme === 'dark' ? {\n    color: 'white',\n    backgroundColor: 'black'\n  } : {};\n  return \u003cdiv style={style}\u003e{todo.title}\u003c/div\u003e;\n};\n\nconst TodoItemContainer = connectRouter(TodoItem);\n```\n\nYou should only use this if you want your component to be updated for _all_ routing state changes. For example, the second example will update during routing transition, whereas the second will only update when the current route is changed.\n\n## Custom middleware\n\nHere's an example of custom middleware that would require the user to login.\n\n```js\nimport { ROUTE_TO_NEXT, findRoutes, routeTo } from 'redux-router-kit';\n\nconst routes = {\n  '/': {\n    component: HomePage\n  }\n  '/me': {\n    component: AccountDetails,\n    requiresLogin: true\n  }\n};\n\nconst createLoginMiddleware = ({routes}) =\u003e {\n  const middleware = store =\u003e next =\u003e action =\u003e {\n    if (!action || !action.type) {\n      return next(action);\n    }\n\n    if (!action.type === ROUTE_TO_NEXT) {\n      const matchedRoutes = findRoutes(routes, action.meta.routeKey);\n      if (matchedRoutes.some(route =\u003e route.requiresLogin)) {\n        const { account } = store.getState();\n        if (!account.isLoggedIn) {\n          return dispatch(routeTo('/login'));\n        }\n      }\n    }\n\n    return next(action);\n  };\n  return middleware;\n};\n\nconst store = createStore(\n  reducer,\n  applyMiddleware(\n    createRouterMiddleware({routes}),\n    createLoginMiddleware({routes})\n  )\n);\n```\n\n## Async route loading\n\nTo load routes asynchronously, just add a `fetch` property to your route.\n\n```js\nconst routes = {\n  '/': HomePage,\n  '/todos': TodoApp,\n  '/developer': {\n    // This is a big page, and we don't want it loaded for everyone.\n    fetch() {\n      return System.import('developerRoutes');\n    }\n  }\n}\n```\n\nThe result of the fetch will be used in place of that route. The routing table in middleware will be modified with the new route, and the url will be retried against the new routing table. (And any nested async routes will also be fetched.) If you need the routing table outside middleware, you can listen to changes.\n\n```js\nconst routerMiddleware = createRouterMiddleware({routes});\n\nconst store = createStore(\n  reducer,\n  applyMiddleware(\n    routerMiddleware\n  )\n);\n\nrouterMiddleware.onRoutesChanged(routes =\u003e {\n  // do something with these routes, like pass them to components or other middleware that need them\n});\n```\n\nIf you'd like to be in control of fetching routes, you can pass a `fetchRoute` function into the middleware.\n\n```js\nconst routes = {\n  '/': HomePage,\n  '/todos': TodoApp,\n  '/developer': {\n    // Can be any truthy value.\n    fetch: true\n  }\n}\n\nconst fetchRoute = route =\u003e {\n  // Return fetched route.\n};\n\nconst routerMiddleware = createRouterMiddleware({routes, fetchRoute});\n```\n\n### Async route loading state\n\nWhile loading async routes, `router.fetch` will be set in state. Because routes aren't yet loaded, the params/etc. will be incomplete.\n\n## Server-side/static rendering\n\nFor server-side or static rendering, just use RouterContainer instead of RouterHistoryContainer.\n\n```js\nconst Home = createReactClass({\n  render() {\n    return \u003cdiv\u003eHome\u003c/div\u003e;\n  }\n});\nconst routes = {\n  '/': Home\n};\nconst store = createStore(\n  combineReducers({\n    router: routerReducer\n  }),\n  applyMiddleware(\n    createRouterMiddleware({routes})\n  )\n);\nreturn store.dispatch(routeTo('/'))\n  .then(() =\u003e {\n    const htmlString = renderToStaticMarkup(\n      \u003cProvider store={store}\u003e\n        \u003cRouterContainer routes={routes}/\u003e\n      \u003c/Provider\u003e\n    );\n    // htmlString is now: \u003cdiv\u003eHome\u003c/div\u003e\n  });\n```\n\n## Thanks!\n\nRedux Router Kit heavily borrows ideas from React Router (https://github.com/reactjs/react-router).\n\nThe History component borrows heavily from https://github.com/cerebral/addressbar and https://github.com/christianalfoni/react-addressbar.\n\nInternally, history (https://github.com/mjackson/history) is used, and it's pretty awesome that it's separate from React Router. :-)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzapier%2Fredux-router-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzapier%2Fredux-router-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzapier%2Fredux-router-kit/lists"}