Redux First History - Redux history binding support react-router - @reach/router - wouter
npm install redux-first-history
Redux First History - Make Redux 100% SINGLE-AND-ONLY source of truth!
Redux history binding for
* react-router
* @reach/router
* wouter
* react-location
* Mix react-router - @reach/router - wouter - react-location in the same app! See Demo.
Compatible with immer - redux-immer - redux-immutable.
:tada: A smaller, faster, optionated, issue-free alternative to connected-react-router
- Main Goal
- Demo
- Main Features
- Installation
- Usage
- Options
- Advanced Config
- Feedback
- Credits & Inspiration
- Contributors
- License
redux and react-router.So you may have components that take location from the redux store, others that take location from router context, and others from withRouter HOC.
This can generate sync issues, due to the fact that many components are updated at different times.
In addition, React shallowCompare rendering optimization will not work as it should.
With redux-first-history, you can mix components that get history from wherever,
they will always be tunneled to state.router.location !
Use whatever you like. History will just work as it should.
``javascript
//react-router v5 - v6
useLocation() === state.router.location
//react-router v5
this.props.history.location === state.router.location
this.props.location === state.router.location
withRouter.props.location === state.router.location
//react-router v4
this.context.router.history.location === state.router.location
this.context.route.location === state.router.location
//@reach/router
this.props.location === state.router.location
//wouter - pathname
useLocation()[0] === state.router.location.pathname
`
Mix redux, redux-saga, react-router, @reach/router, wouter and react-location
without any synchronization issue!
Why? Because there is no synchronization at all! There is only one history: reduxHistory!
* One way data-flow
* One unique source of truth
* No more location issues!

- react-router v6: https://wvst19.csb.app/
- Source: https://codesandbox.io/s/redux-first-history-demo-rr6-forked-wvst19
- react-router v5: https://wy5qw1125l.codesandbox.io/
- Source: https://codesandbox.io/s/wy5qw1125l
and push actions (taken from RRR)
* Handle Redux Travelling from dev tools (that's nonsense in production, but at the end of the day this decision it's up to you ...)
Installation
Using npm: $ npm install --save redux-first-history
Or yarn:
$ yarn add redux-first-history
Usage
store.js
`javascript
import { createStore, combineReducers, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import { createReduxHistoryContext, reachify } from "redux-first-history";
import { createWouterHook } from "redux-first-history/wouter";
import { createBrowserHistory } from 'history';const { createReduxHistory, routerMiddleware, routerReducer } = createReduxHistoryContext({
history: createBrowserHistory(),
//other options if needed
});
export const store = createStore(
combineReducers({
router: routerReducer
//... reducers //your reducers!
}),
composeWithDevTools(
applyMiddleware(routerMiddleware)
)
);
export const history = createReduxHistory(store);
//if you use @reach/router
export const reachHistory = reachify(history);
//if you use wouter
export const wouterUseLocation = createWouterHook(history);
`store.js (with @reduxjs/toolkit)
`javascript
import { combineReducers } from "redux";
import { configureStore } from "@reduxjs/toolkit";
import { createReduxHistoryContext } from "redux-first-history";
import { createBrowserHistory } from "history";const {
createReduxHistory,
routerMiddleware,
routerReducer
} = createReduxHistoryContext({ history: createBrowserHistory() });
export const store = configureStore({
reducer: combineReducers({
router: routerReducer
}),
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(routerMiddleware),
});
export const history = createReduxHistory(store);
`app.js
`javascript
import React, { Component } from "react";
import { Provider, connect } from "react-redux";
import { Router } from "react-router-dom";
import { store, history } from "./store";const App = () => (
//.....
);
export default App;
`app.js (react-router v6)
`javascript
import React, { Component } from "react";
import { Provider } from "react-redux";
import { HistoryRouter as Router } from "redux-first-history/rr6";
import { store, history } from "./store";const App = () => (
//.....
);
export default App;
`
saga.js (react-saga)
`javascript
import { put } from "redux-saga/effects";
import { push } from "redux-first-history";function* randomFunction() {
//....
yield put(push(YOUR_ROUTE_PATH));
//....
}
`slice.js (in a Thunk with @reduxjs/toolkit)
`javascript
import { push } from "redux-first-history";export const RandomThunk = (dispatch) => {
//....
dispatch(push(YOUR_ROUTE_PATH));
//....
}
`
* Just a simple Router with no more ConnectedRouter!
* Probably, you already did connect the Redux store with
react-router-redux or connected-react-router (in this case you have only to replace the import!)
Options
`javascript 1.8
export const createReduxHistoryContext = ({
history,
routerReducerKey = 'router',
reduxTravelling = false,
selectRouterState = null,
savePreviousLocations = 0,
batch = null,
reachGlobalHistory = null
})
`|key | optional |description |
|--- |---|--- |
|history | no| The
createBrowserHistory object - v4.x/v5.x |
|routerReducerKey | yes | if you don't like router name for reducer.
|reduxTravelling | yes | if you want to play with redux-dev-tools :D.
|selectRouterState |yes | custom selector for router state. With redux-immutable selectRouterState = state => state.get("router")
|savePreviousLocations |yes | if > 0 add the key "previousLocation" to state.router, with the last N locations. [{location,action}, ...]
|batch |yes | a batch function for batching states updates with history updates. Prevent top-down updates on react : usage import { unstable_batchedUpdates } from 'react-dom'; batch = unstable_batchedUpdates
|reachGlobalHistory |yes | globalHistory object from @reach/router - support imperatively navigate of @reach/router - import { navigate } from '@reach/router' : usage import { globalHistory } from '@reach/router'; reachGlobalHistory = globalHistory
|basename | no| support basename (history v5 fix) | Advanced Config
* Support "navigate" from @reach/router
`javascript
import { createReduxHistoryContext, reachify } from "redux-first-history";
import { createBrowserHistory } from 'history';
import { globalHistory } from '@reach/router';const { createReduxHistory, routerMiddleware, routerReducer } = createReduxHistoryContext({
history: createBrowserHistory(),
reachGlobalHistory: globalHistory,
//other options if needed
});
`* React batch updates: top-down batch updates for maximum performance. Fix also some updated edge cases.
`javascript
import { createReduxHistoryContext, reachify } from "redux-first-history";
import { createBrowserHistory } from 'history';
import { unstable_batchedUpdates } from 'react-dom';const { createReduxHistory, routerMiddleware, routerReducer } = createReduxHistoryContext({
history: createBrowserHistory(),
batch: unstable_batchedUpdates,
//other options if needed
});
``Let me know what do you think!
Enjoy it? Star this project! :D