npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-first-history

v5.2.0

Published

Redux First History - Redux history binding support react-router - @reach/router - wouter

Downloads

306,816

Readme

redux-first-history

Redux history binding for

Compatible with immer - redux-immer - redux-immutable.

:tada: A smaller, faster, optionated, issue-free alternative to connected-react-router

Table of Contents

Main Goal

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!

  • One way data-flow
  • One unique source of truth
  • No more location issues!

Demo

  • 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

Main Features

  • 100% one source of truth (store)
  • No synchronization depending on rendering lifecycle (ConnectedRouter)
  • No React dependency (we want history to be always in store!)
  • 100% one-way data flow (only dispatch actions!)
  • Improve React shallowCompare as there is only one "location"
  • Support react-location 3.x
  • Support react-router v4 / v5 / v6
  • Support @reach/router 1.x
  • Support wouter 2.x
  • Support mix react-router, @reach/router & wouter in the same app!
  • Fast migration from an existing project, with the same LOCATION_CHANGE 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

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));
  //....
}
  • 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

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
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.
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 
});

Feedback

Let me know what do you think! Enjoy it? Star this project! :D

Credits & Inspiration

  • redux-first-routing
  • react-router-redux
  • connected-react-router

Contributors

See Contributors.

License

MIT License.