Redux First History - Make Redux 100% SINGLE-AND-ONLY source of truth!
Redux history binding for
react-router
@reach/router
wouter
react-router
- @reach-router
- wouter
in the same app!! Demo : https://wy5qw1125l.codesandbox.io/.Compatible with immer
- redux-immer
- redux-immutable
.
🎉 A smaller, faster, optionated, issue-free alternative to
connected-react-router
Use whatever you like. History will just work as it should.
//react-router v4 - v5
this.context.router.history.location === state.router.location
this.context.route.location === state.router.location
this.props.history.location === state.router.location
this.props.location === state.router.location
withRouter.props.location === state.router.location
//@reach/router
this.props.location === state.router.location
//wouter - pathname
useLocation()[0] === state.router.location.pathname
You can mix redux, redux-saga, react-router, @reach/router & wouter
without any synchronization issue!
Why? Because there is no synchronization at all! There is only one history: reduxHistory!
Demo : https://wy5qw1125l.codesandbox.io/ src: https://codesandbox.io/s/wy5qw1125l
LOCATION_CHANGE
and push actions (taken from RRR)Using npm:
$ npm install --save redux-first-history
Or yarn:
$ yarn add redux-first-history
store.js
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);
app.js
import React, { Component } from "react";
import { Provider, connect } from "react-redux";
import { push } from "redux-first-history";
import { Router } from "react-router-dom";
import { store, history } from "./store";
const App = () => (
<Provider store={store}>
<Router history={history}>
//.....
</Router>
</Provider>
);
export default App;
push
action creator from redux-first-history
if you need to dispatch location from saga
or connected components.react-router-redux
or connected-react-router
(in this case you have only to replace the import!)While working with relatively large projects, it's quite common to use both redux
and react-router
.
So you may have components that take location from store, others that take location from router context, and others from withRouter HOC.
This sometimes could generate sync issue, due to the fact that many components are updated at different time. 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 !
export const createReduxHistoryContext = ({
history,
routerReducerKey = 'router',
oldLocationChangePayload = false,
reduxTravelling = false,
selectRouterState = null,
savePreviousLocations = 0,
batch = null,
reachGlobalHistory = null
})
key | optional | description |
---|---|---|
history | no | The createBrowserHistory object - currently tested only with version 4.7.2 - 4.10.1 |
routerReducerKey | yes | if you don't like router name for reducer. |
oldLocationChangePayload | yes | if you use the oldLOCATION_CHANGE payload{ ...location } instead of the new{ location } , setting this flag will make you able to not change your app at all! (removed in v5) |
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
|
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
});
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
See Contributors.