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 ?
-
Yes, 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
2 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
navigatemethod 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.
- 3,903
- 2
- 18
- 28
-
-
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
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