connected-react-router
Redux bindings that keep React Router's history in sync with your store, both ways.
Repository Health
Technical Analysis
Connected React Router wires React Router v4/v5 into a Redux store so that navigation is just another piece of application state. It listens to history changes and dispatches them into the store, and it also watches the store for changes coming from Redux DevTools time-travel debugging, pushing the corresponding location back onto history so both stay consistent in either direction.
The library ships a structure abstraction that lets the exact same reducer, selector, and component code run against plain JavaScript objects, Immutable.js, or seamless-immutable state, so teams that already use one of those state containers don’t need adapter code of their own. It’s a small, focused binding layer rather than a router or a state manager in its own right — most apps use it alongside redux-thunk or redux-saga to dispatch history methods like push, replace, go, goBack, and goForward from anywhere in their action creators.
What You Get
- A
ConnectedRoutercomponent that replacesBrowserRouter/NativeRouterand drives React Router from a Redux-managed history - A
connectRouter(history)reducer factory that adds router state (location, action) to your root reducer under the requiredrouterkey routerMiddleware(history)sopush,replace,go,goBack, andgoForwardcan be dispatched as plain Redux actions from thunks or sagas- Selectors (
getLocation,getAction,getSearch,getHash,createMatchSelector) for reading router state out of the store - Support for Immutable.js and seamless-immutable state trees via dedicated entry points that share the same reducer/selector logic
- Redux DevTools time-travel compatibility, including detection to avoid double-dispatching location changes on the initial render
Common Use Cases
- Reading the current route/location from anywhere in the component tree via
connect()instead of prop-drillinghistory - Triggering navigation from a
redux-sagaorredux-thunkaction creator after an async operation completes (e.g. redirect after login) - Time-travel debugging a full app, including route changes, with Redux DevTools
- Server-side rendering apps that still need
StaticRouteron the server while usingConnectedRouteron the client - Migrating off the unmaintained
react-router-reduxpackage while keeping a similar reducer shape
Under The Hood
Architecture
The library is a lean, single-purpose binding layer with no internal framework of its own: src/index.js wires together ConnectedRouter.js (the connected component), reducer.js (the router reducer factory), selectors.js, and actions.js (LOCATION_CHANGE, push/replace/go/etc). The core flow is uni-directional — ConnectedRouter listens to history.listen and dispatches onLocationChanged into Redux, while reducer.js merges the resulting LOCATION_CHANGE payload into store state — but it’s closed into a loop by a second listener that subscribes to store changes, detects when a location changed via Redux DevTools time-travel rather than a real navigation (via an inTimeTravelling flag and comparing history’s location against the store’s), and re-drives history.push to match. The standout structural choice is src/structure/{plain,immutable,seamless-immutable}, a set of parameterized fromJS/merge functions injected into the createConnectedRouter/createConnectRouter/createSelectors factories so the same reducer and component logic works unchanged across three different state representations.
Tech Stack
Plain Babel-transpiled JavaScript (ES6+) with peer dependencies on react (^16.4/^17.0), react-redux (^6.0/^7.1), react-router (^4.3/^5.0), redux (^3.6/^4.0), and history (^4.7.2); runtime dependencies are minimal (prop-types, lodash.isequalwith), with immutable and seamless-immutable as optional peers for the alternate structure modules. The build produces dual ESM/CommonJS output via Babel plus a UMD bundle via Webpack 4. Type consumers get hand-written .d.ts declaration files rather than generated types. CI runs on Travis (testing against several legacy Node versions) plus a separate GitHub CodeQL workflow — there’s no modern GitHub Actions test pipeline.
Code Quality
Tests live under test/ across five files covering actions, the ConnectedRouter component (by far the largest suite), middleware, the reducer, and selectors, using Jest with Enzyme (React 16 adapter) and redux-mock-store — a reasonably thorough suite for a library this size. Linting is configured via ESLint with eslint:recommended plus the import and react plugins, wired to run automatically after tests via a posttest script. Error handling is mostly implicit; one spot throws a raw string rather than an Error instance when react-redux’s context is missing, which is a minor code smell. Source is plain JS with no static typing, though consumer-facing types are maintained by hand.
What Makes It Unique
The bidirectional time-travel handling is the library’s most distinctive piece of engineering: rather than only pushing history into the store, it also detects when a change originated from Redux DevTools rewinding/replaying state and reconciles history to match, closing a loop that most router-Redux bindings leave one-directional. Combined with the pluggable structure abstraction for Immutable.js/seamless-immutable support, it solves a fairly specific integration problem — keeping two independent sources of truth (browser history and Redux store) consistent under debugging tools — without asking consumers to write any adapter code themselves.