Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
redux-first-history
Advanced tools
Redux First History - Redux history binding support react-router - @reach/router - wouter
Redux First History - Make Redux 100% SINGLE-AND-ONLY source of truth!
Redux history binding for
react-router
@reach/router
wouter
react-location
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
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 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.
//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!
react-router v6: https://wvst19.csb.app/
react-router v5: https://wy5qw1125l.codesandbox.io/
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);
store.js (with @reduxjs/toolkit)
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
import React, { Component } from "react";
import { Provider, connect } from "react-redux";
import { Router } from "react-router-dom";
import { store, history } from "./store";
const App = () => (
<Provider store={store}>
<Router history={history}>
//.....
</Router>
</Provider>
);
export default App;
app.js (react-router v6)
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 = () => (
<Provider store={store}>
<Router history={history}>
//.....
</Router>
</Provider>
);
export default App;
saga.js (react-saga)
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)
import { push } from "redux-first-history";
export const RandomThunk = (dispatch) => {
//....
dispatch(push(YOUR_ROUTE_PATH));
//....
}
react-router-redux
or connected-react-router
(in this case you have only to replace the import!)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) |
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.
FAQs
Redux First History - Redux history binding support react-router - @reach/router - wouter
The npm package redux-first-history receives a total of 62,248 weekly downloads. As such, redux-first-history popularity was classified as popular.
We found that redux-first-history demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.