react-router: [Bug]: How to navigate outside React context in v6?
What version of React Router are you using?
v6
Steps to Reproduce
In v6 docs, it mentions that we can use useNavigate()
hook to do navigation, similar to in v5 we directly use useHistory()
hook. However I am not sure how we can do the navigation outside React context in v6, cause in v5, your can manually provide a history
object as a prop to Router
component, and reference it in other places even it is not inside React context:
// myHistory.js
import { createBrowserHistory } from "history";
const customHistory = createBrowserHistory();
export default customHistory
// otherFile.js
import history from './myHistory.js'
function doSomething() {
history.push('/xxx')
}
But how we could achieve the same functionality in v6? I have see a few posts asking similar questions on stackoverflow, but currently there is no solution provided:
- React Router v6.0.0-alpha.5 history prop removal - navigating outside of react context
- React Router ^6.0.0-beta.0 history prop removal - navigating outside of react context
Expected Behavior
A common scenario from my experience was consider i have a redux thunk action creator that doing signup logic, and after sending request if success i wish the page can be navigate to home page:
// signupActions.js
export const signupRequest = (userData) => {
return dispatch => {
dispatch(signupPending())
return axios.post('/api/user', userData)
.then(res => dispatch(signupSuccess()))
.then(() => {
// doing navigation
// navigate('/')
})
.catch(error => dispatch(signupFailure(error.message)))
}
}
The action is outside React context so i am not able to use useNavigate()
hook, Although i can do some refactor and move some logic to the React component, but i prefer to keep most business logic inside action since i wish the component are more responsible for UI rendering.
Actual Behavior
As mentioned above
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 53
- Comments: 56 (10 by maintainers)
Short answer: When your thunk is successful, change the state to something like
"success"
or"redirect"
and then useEffect + navigate:You can now use HistoryRouter (as of version 6.1.0) to maintain a global history instance that you can access anywhere:
Can we reopen this issue?
@ryanflorence it still doesn’t answer the original question though, which is how to programmatically use navigation outside React components.
I believe it’s not about one particular example that can be fixed with a different workflow, but a more general and pretty demanded functionality that is missing from the new version, if the library is to be called a ‘fully-featured’ one. Would love to see a guide on how it can be done.
Related: https://github.com/remix-run/react-router/pull/8284, https://github.com/remix-run/react-router/issues/7970.
@timdorr after this much time has passed, I think it’s not really good to keep the
unstable_
flag for theHistoryRouter
. Why not just re-exportinghistory
orcreateBrowserHistory
inreact-router-dom
? This way devs can be sure they are using the samehistory
version as react-router because it is coming directly from there.source code reference: https://github1s.com/remix-run/react-router/blob/HEAD/packages/react-router-dom/index.tsx#L133
temporary plan, waiting for official support:
// history.ts
// BrowserRouter.tsx
I had this problem and created custom
HistoryRouter
like that:@ryanflorence do you think this is something react router v6 will support natively? We are also running into this issue. Crucial use case for us: if an API returns 401, token has expired, we want to redirect user to login screen with a message. We previously were able to do this pretty easily. This has become challenging now that the history object is no longer being exposed (and we can’t use hooks in the API request code block)
@amazecc and @huczk thanks for your comments 😃
It works 👍
history.ts
HistoryRouter.ts
Than all you need is to wrap the
<App />
with<HistoryRouter/>
.and use your
myHistory
where you want:This is a really common use case, so do we have a solution for this? Ideally, something that could be done in 1 or 2 lines of code like how it should be like, instead of requiring devs to moving backwards to
unstable_HistoryRouter
, which is obviously something v6 discourages devs from doing?At the moment, the best solution for me if I stick to v6 is not to replace the root
<Router />
(I don’t think using unstable_HistoryRouter is what I look forward to in production) but to manually pass anavigate
hook from each component. This creates a lot of code redundancy and is much more prone to errors. Same thing for the solution presented by @ryanflorence, especially when you have to deal with the extra state/useEffect hook to handle simple programmatic navigation.@timdorr According to the docs, the
history
package is no longer a peer dependency we need to have installed and it’s rather a direct dependency ofreact-router-dom
(link)So you are suggesting that we import a transitive dependency, which I do not like very much as a solution. (pnpm would also throw an error on this)
What if the
react-router-dom
package exported history-related utils itself instead?have any offical sulution? history module is not available in v 6.4
Meanwhile, in Remixland…
There’s the newly introduced “redirect” method to redirect from outside of the component. it requires version 6.4 I think it might help …
https://reactrouter.com/en/main/fetch/redirect
Before react router v6.4, I used this way to navigating outside of components.
But after update, history router no more available.
Is there a way now to navigating outside of components, without rollback to 6.3 version?
I’ll make a quick guide on this, it’s going to be a common question that we’ve already anticipated.
React Router v6 introduces a new navigation API that is synonymous with <Link> and provides better compatibility with suspense-enabled apps.
as described in the official docs, does this mean that i’d still be using history api and not navigate api, if i use unstable_HistoryRouter so there is now way to do it with navigate api outside react components ?
The
unstable_
prefix just signifies you could encounter bugs due to mismatched versions ofhistory
from whatreact-router
requests itself. While the practical implications are probably going to just create type issues (different types between versions ofhistory
), there could also be API or behavior changes that cause more subtle bugs (such as how URLs are parsed, or how paths are generated).As long as you’re keeping an eye on the version of
history
requested byreact-router
and ensuring that’s in sync with the version used by your app, you should be free of issues. We don’t yet have protections or warnings against the mismatch, hence we’re calling it unstable for now.@luwanhuang The above code only works when user switch to new router path.
We have refactored our code, use
window.postMessage('NotAuthorized');
in axios.interceptors code, then listen to the message and do navigation.Thanks for your reply, I just deleted my comment and double checked my code, and find out the real workaround is the following :
then use
useEffect
in a global functional component, eg HomeView to detect if the app need to redirect to ‘/login’ page ,Let me summarize the key idea: even if you can’t do navigation outside React context use react-router v6, but you can change a shared state(in our case, it’s localStorage.userInfo) there, then you can do navigation in a global/shared functional component.
This is what I came up with for a simple solution… seems to work OK.
Requirement: you need to call
useExtNavigate
from somewhere inside your React Router context, only then can you use this by callingextNavigate('/path')
Great, thanks from me too. I am actually using myHistory.replace/.push INSIDE of react components, as it does not trigger a re-render of all components (like useNavigate does), when the route changes. Hope this gets official support…