I'm using redux saga & react router v6 and I want to redirect to a route from one of my sagas, is there a way to do it ?
-
1Yes, you'll need to create a custom router and history object to do this. Can you update your question to include a [minimal, complete, and reproducible code example](https://stackoverflow.com/help/minimal-reproducible-example) of your code and any attempt to do this on your own first? – Drew Reese Jan 27 '22 at 16:17
-
What do you mean by a custom router ? What I need is from my saga function make a redirect to a route. In a previous version of react you can make `yield put(push(ROUTE))` with connected-react-router library, but it doesn't support v6 of react router @dre – Avedis Maroukian Jan 27 '22 at 16:30
-
Correct. I mean, you implement a custom router using the low-level `Router` in order to provide the custom history object. You can then use this history object as you need elsewhere outside the router/react code. If you need to, create your custom asynchronous navigation actions. My answer [here](https://stackoverflow.com/a/70012117/8690857) may help with the router part, pulling the history object in to issue imperative navigation is left to do. – Drew Reese Jan 27 '22 at 16:48
5 Answers
There are multiple options
1 - Sending the navigate method as part of dispatched redux action
// component
const navigate = useNavigate()
dispatch({type: FOO, navigate})
// saga
yield takeEvery(FOO, function*(action) {
action.navigate('/foo')
})
Pros:
- You are using the
navigate
method which is recommended by the react-router team - The API is unlikely to change
Cons
- You have access to the navigate method only in specific sagas that received such action
- You have unserializable data in your actions
2 - Another option is to store the navigate
method in some way. E.g. you can create a dummy react component that will get the navigate method through useNavigate
hook and then store it in some "global" variable. See this SO answer for a possible solution:
https://stackoverflow.com/a/70002872/2347716
This deals with the the cons from previous solution, but there are still some issues:
- You need your React tree to render at least once before you have access to the navigate method
- You are adding non-view complexity to your view layer by introducing the dummy component
3 - There is another solution, similar to what we had with react-router 5 that deals with the issues in the previous solution. It is to use the history
object. It is not documented since its unstable, but there is a HistoryRouter implementation as part of the react-router-dom package. See https://github.com/remix-run/react-router/releases/tag/v6.1.1
import {unstable_HistoryRouter as HistoryRouter} from 'react-router-dom'
import { createBrowserHistory } from "history";
const history = createBrowserHistory()
// saga setup
sagaMiddleware.runSaga(rootSaga, history);
// react
<HistoryRouter history={history} />
The issue with this solution is that it is unstable because it might have some issues with some of React 18 features. Personally I prefer it since it solves everything else and we can deal with React 18 issues once its actually released and we know what they are.

- 4,702
- 2
- 20
- 33
-
-
Small note that #1 only works if you call the action directly from a component - not another saga. – David Apr 17 '22 at 09:17
-
@David, do you know of a way to make #1 work when passing down the navigate function through multiple sagas? I.e., the component dispatches an action with navigate but then that saga calls another saga, also passing down the same navigate. – Aerophite Jun 21 '22 at 20:59
-
1@Aerophite just pass it as a parameter: `yield call(anotherSaga, navigate)` -> `function* anotherSaga(navigate){ ... }` – Martin Kadlec Jun 21 '22 at 21:28
-
My solution
// "HistoryRouter" implementation
import * as React from 'react'
import type {BrowserHistory} from 'history'
import {Router} from 'react-router-dom'
export interface HistoryRouterProps {
history: BrowserHistory
basename?: string
children?: React.ReactNode
}
export function HistoryRouter({
basename,
children,
history,
}: HistoryRouterProps) {
let [state, setState] = React.useState({
action: history.action,
location: history.location,
})
React.useLayoutEffect(() => history.listen(setState), [history])
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
)
}
// Use
import {createBrowserHistory} from 'history'
export const history = createBrowserHistory()
ReactDOM.render(
<HistoryRouter history={history}>
<Routes>
<Route path="login" element={<LoginComponent />} />
<Route path="register" element={<RegisterComponent />} />
<Route path="*" element={<HomeComponent />} />
</Routes>
</HistoryRouter>
, root)
// call history push in file saga
function* fetchLoginSaga(action: FetchLoginRequest) {
try {
yield call(history.push, "/home")
} catch (e: any) {
}
}

- 1
- 1
- 1
I present my solution to the problem, maybe it is a bit convoluted but it is very clean and useful:
All of us will have a parent component in the routes, in my case I have called it App, and we can imagine that we will have this component connected to redux, therefore the following tree would remain:
index.js > Provider > Router > App (with Outlet) > Child Route.
That means that we can create a yield put that changes a props in the parent component, in my case App, which will contain a useEffect that will launch the navigate:
import { useEffect } from 'react'
import { Outlet, useNavigate } from "react-router-dom";
const AppContainer = ({ url }) => {
const navigate = useNavigate()
useEffect(()=>{
if (url) {
navigate(url)
}
}, [url])
return (
<div>
<Outlet />
</div>
)
}
export default AppContainer;
Now whenever I want to navigate from the sagas I simply have to execute a yield put, in this way the props url is modified and the useEffect is launched that sends me to the requested url.
Since my SPA system is a bit complex, I'll leave you to solve that part of connecting the dispatch with the component

- 434
- 3
- 11
There is an option that works well for me. You should just take a router (the result of createBrowserRouter()
call) and set it to sagaMiddleware
context and then use it from saga wherever you want.
App.tsx
const router = createBrowserRouter([
{
path: '/',
element: <div>Home</div>
},
]);
sagaMiddleware.setContext({
router,
});
export const App = () => {
return (
<RouterProvider router={router}/>
);
}
saga.ts
export function* selectSmthSaga(action: ActionType<typeof selectSmthAction>) {
const router: ReturnType<typeof createBrowserRouter> = yield getContext('router');
router.navigate(`/path/${action.payload}/subpath`);
}

- 13
- 1
- 3
you can use @lagunovsky/redux-react-router instead of connected react router because it's support react router v6
Update: Here's a link to the package which provides further documentation for migrating from conncted-react-router https://www.npmjs.com/package/@lagunovsky/redux-react-router#user-content-migrate-from-connected-react-router

- 552
- 3
- 17
-
1Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 30 '22 at 19:43