npm package: @lagunovsky/redux-react-router
Description: A Redux binding for React Router v6
Author: Ivan Lagunovsky
Latest version: 4.5.0

![License](https://img.shields.io/github/license/lagunovsky/redux-react-router)
![TypeScript](https://img.shields.io/badge/typescript-%3E%3D4.0.0-blue)
![Tests workflow](https://github.com/lagunovsky/redux-react-router/actions/workflows/publish.yml/badge.svg)

Redux React Router
======================

A Redux binding for React Router

- [Main features](#main-features)
- [Installation](#installation)
- [Usage](#usage)
- - [Examples](#examples)
- - [API](#api)
- [Migrate from Connected React Router](#migrate-from-connected-react-router)

## Main features

- Synchronize router state with redux store through uni-directional flow (i.e. history -> store -> router -> components).
- Supports [React Router v7](https://github.com/remix-run/react-router/tree/main) and [History v5](https://github.com/remix-run/history)
- Supports functional component hot reloading while preserving state.
- Dispatching of history methods (`push`, `replace`, `go`, `back`, `forward`) works for both [redux-thunk](https://github.com/reduxjs/redux-thunk)
  and [redux-saga](https://github.com/redux-saga/redux-saga).
- Nested children can access routing state such as the current location directly with `react-redux`'s `connect`.
- Supports time traveling in Redux DevTools.
- TypeScript

## Installation

Redux React Router requires **React 16.8, React Redux 6.0, React Router 6.0 or later**.

```shell
npm install --save @lagunovsky/redux-react-router
```

```shell
yarn add @lagunovsky/redux-react-router
```

## Usage

### Examples

Note: the `history` object provided to reducer, middleware, and component must be the same `history` object.

#### `@reduxjs/toolkit`

```typescript jsx
import { createRouterMiddleware, createRouterReducerMapObject, push, ReduxRouter } from '@lagunovsky/redux-react-router'
import { configureStore } from '@reduxjs/toolkit'
import { createBrowserHistory } from 'history'
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider, useDispatch } from 'react-redux'
import { Route, Routes } from 'react-router'
import { NavLink } from 'react-router-dom'

const history = createBrowserHistory()
const routerMiddleware = createRouterMiddleware(history)

const store = configureStore({
  reducer: createRouterReducerMapObject(history),
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(routerMiddleware),
})

function Content() {
  const dispatch = useDispatch()

  const onClickHandler = () => {
    const action = push(Date.now().toString())
    dispatch(action)
  }

  return (
    <>
      <NavLink to={'/'} children={'Home'}/>
      <button type={'button'} onClick={onClickHandler} children={'Rand'}/>
    </>
  )
}

function App() {
  return (
    <Provider store={store}>
      <ReduxRouter history={history}>
        <Routes>
          <Route path={'*'} element={<Content/>}/>
        </Routes>
      </ReduxRouter>
    </Provider>
  )
}
```

#### `redux.createStore` and custom selector

```typescript jsx
import { createRouterMiddleware, createRouterReducer, push, ReduxRouter, ReduxRouterSelector } from '@lagunovsky/redux-react-router'
import { createBrowserHistory } from 'history'
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider, useDispatch } from 'react-redux'
import { Route, Routes } from 'react-router'
import { NavLink } from 'react-router-dom'
import { applyMiddleware, combineReducers, compose, createStore } from 'redux'

const history = createBrowserHistory()
const routerMiddleware = createRouterMiddleware(history)

const rootReducer = combineReducers({ navigator: createRouterReducer(history) })

const store = createStore(rootReducer, compose(applyMiddleware(routerMiddleware)))
type State = ReturnType<typeof store.getState>

const routerSelector: ReduxRouterSelector<State> = (state) => state.navigator

function App() {
  return (
    <Provider store={store}>
      <ReduxRouter history={history} routerSelector={routerSelector}>
        <Routes>
          <Route path={'*'} element={<div/>}/>
        </Routes>
      </ReduxRouter>
    </Provider>
  )
}
```

### API

#### Types

```typescript jsx
type ReduxRouterProps = {
  history: History
  basename?: string
  children?: React.ReactNode
  routerSelector?: ReduxRouterSelector 
}
```

```typescript jsx
type ReduxRouterState = {
  location: history.Location
  action: history.Action
}
```

#### Constants

```typescript jsx
const ROUTER_REDUCER_MAP_KEY = 'router'
const ROUTER_CALL_HISTORY_METHOD = '@@router/CALL_HISTORY_METHOD'
const ROUTER_ON_LOCATION_CHANGED = '@@router/ON_LOCATION_CHANGED'
```

#### `createRouterMiddleware(history: History, options?: RouterMiddlewareOptions) => Middleware`

A middleware you can apply to your Redux store to capture dispatched actions created by the action creators. 
It will redirect those actions to the provided history instance.

If you need the middleware to pass the action on after being handled, you can provide the `hasNextCall` option as `true`.

#### `createRouterReducerMapObject(history: History) => {router: Reducer<ReduxRouterState>}`

Creates a reducer map object that stores location updates from history.

#### `createRouterReducer(history: History) => Reducer<ReduxRouterState>`

Creates a reducer function that stores location updates from history.
Note: If you create a reducer with a key other than `ROUTER_REDUCER_MAP_KEY`, 
you must add a selector `(state: State) => ReduxRouterState` to your `<ReduxRouter/>` as `routerSelector` prop.

#### `reduxRouterSelector(state: State): ReduxRouterState`

Selector that returns location updates from history.

### Action creators

Same as [history methods](https://github.com/remix-run/history/blob/dev/docs/navigation.md)

- `push`
- `replace`
- `go` 
- `back`
- `forward`

By default, history methods calls in middleware are wrapped in a `queueMicrotask`. If you want to avoid it, please use the following methods:

- `pushStraight` 
- `replaceStraight` 
- `goStraight` 
- `backStraight` 
- `forwardStraight`

## Migrate from Connected React Router

```diff
- import { connectRouter } from 'connected-react-router'
+ import { createRouterReducer } from '@lagunovsky/redux-react-router'

- export const routerReducer = connectRouter(history)
+ export const routerReducer = createRouterReducer(history)
```

```diff
- import { routerMiddleware } from 'connected-react-router'
+ import { createRouterMiddleware } from '@lagunovsky/redux-react-router'

- export const routerMiddleware = routerMiddleware(history)
+ export const routerMiddleware = createRouterMiddleware(history)
```

```diff
- import { ConnectedRouter } from 'connected-react-router'
+ import { ReduxRouter } from '@lagunovsky/redux-react-router'

- <ConnectedRouter history={history} />
+ <ReduxRouter history={history} />
```

```diff
- import { RouterState } from 'connected-react-router'
+ import { ReduxRouterState } from '@lagunovsky/redux-react-router'
```