-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug]: How to navigate outside React context in v6? #8264
Comments
This comment has been minimized.
This comment has been minimized.
I'll make a quick guide on this, it's going to be a common question that we've already anticipated. |
Short answer: When your thunk is successful, change the state to something like export function AuthForm() {
const auth = useAppSelector(selectAuth);
const dispatch = useAppDispatch();
const navigate = useNavigate();
useEffect(() => {
if (auth.status === "success") {
navigate("/dashboard", { replace: true });
}
}, [auth.status, navigate]);
return (
<div>
<button
disabled={auth.status === "loading"}
onClick={() => dispatch(login())}
>
{auth.status === "idle"
? "Sign in"
: auth.status === "loading"
? "Signing in..."
: null}
</button>
</div>
);
} |
@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. |
I had this problem and created custom import { Update } from "history";
import { useLayoutEffect, useReducer } from "react";
import { Router } from "react-router-dom";
// your local created history
import { history } from "./history";
const reducer = (_: Update, action: Update) => action;
export const HistoryRouter: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {
action: history.action,
location: history.location,
});
useLayoutEffect(() => history.listen(dispatch), []);
return (
<Router navigationType={state.action} location={state.location} navigator={history}>
{children}
</Router>
);
}; |
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 import { createBrowserHistory } from "history";
export const history = createBrowserHistory(); // BrowserRouter.tsx import React from "react";
import { History } from "history";
import { BrowserRouterProps as NativeBrowserRouterProps, Router } from "react-router-dom";
export interface BrowserRouterProps extends Omit<NativeBrowserRouterProps, "window"> {
history: History;
}
export const BrowserRouter: React.FC<BrowserRouterProps> = React.memo(props => {
const { history, ...restProps } = props;
const [state, setState] = React.useState({
action: history.action,
location: history.location,
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router {...restProps} location={state.location} navigationType={state.action} navigator={history} />;
}); |
@amazecc and @huczk thanks for your comments :) It works 👍 history.ts// make sure to install `history` https://github.com/remix-run/history/blob/main/docs/installation.md
import { createBrowserHistory } from "history"
export const myHistory = createBrowserHistory({ window }) HistoryRouter.ts// implementation from https://github1s.com/remix-run/react-router/blob/HEAD/packages/react-router-dom/index.tsx#L133-L137
import { ReactNode } from "react"
import { useLayoutEffect, useState } from "react"
import { History } from "history"
import { Router } from "react-router-dom"
export interface BrowserRouterProps {
basename?: string;
children?: ReactNode;
history: History;
}
export function HistoryRouter({
basename,
children,
history
}: BrowserRouterProps) {
let [state, setState] = useState({
action: history.action,
location: history.location
})
useLayoutEffect(() => history.listen(setState), [history])
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />
)
} Than all you need is to wrap the import { HistoryRouter } from "./HistoryRouter"
import { myHistory } from "./history"
ReactDOM.render(
<HistoryRouter history={myHistory}>
<App />
</HistoryRouter>
document.getElementById("root")
) and use your import axios from "axios"
import { myHistory } from "./history"
// Configure axios instance
const backendApi = axios.create(...)
backendApi.interceptors.response.use(function(response) {
return response
}, async function (error) {
if (error.response?.status === 403) {
myHistory.replace(`/forbidden`) // Usage example.
return Promise.reject(error)
}
return Promise.reject(error)
}) |
@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) |
I also encountered this problem, the official seems to be reluctant to support the use of navigation outside the context of react |
What is the official answer for dealing with this issue? |
You can now use HistoryRouter (as of version 6.1.0) to maintain a global history instance that you can access anywhere: import { createBrowserHistory } from 'history';
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
let history = createBrowserHistory();
function App() {
return (
<HistoryRouter history={history}>
// The rest of your app
</HistoryRouter>
);
}
history.push("/foo"); |
@timdorr According to the docs, the 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 |
@timdorr |
Today I had to migrate react-router 5 to 6 and had the same question. Before we used Now I'm asking myself if I't the way to go to use |
useNavigate() hook returns a function. One can also use it as a component. Both ways are demonstrated in the upgrade guide: https://reactrouter.com/en/v6.3.0/upgrading/v5 |
What is the last update on this? How to redirect from a redux slice? |
I'm curious about this too. How can I respond to, for instance, auth status changing in order to safely cause a redirect? Do I just set up an observer inside an overarching react component? Update2: The below gets re-triggered each time a navigation occurs, so that's not a suitable solution either. Back to the drawing board. Update:, I can use this pattern from a carefully chosen component. It does mean the components and logic are together, but I'm all for it if it is more coherent in the v6 design:
|
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? |
have any offical sulution? |
I think you can use |
As I could not find a satisfying way to make it work, decision was to create a hook which will be connected to a global routing state and act like a proxy. Maybe not the best way to achieve it, but works well in our project. One of the benefits of such method - it's unobtrusive, you can keep React ecosystem separately from business logic.
|
Is React Router team planning to provide an alternative for the missing
|
@sergioavazquez Controlling navigation from outside the router is still possible, you just need to maintain a separate |
Ok, but how to link history from this dependency to Router, without “unstable_HistoryRouter”? Or it’s no more needed? It will work without it? |
@andrey779evseev @Arsikod The official solution is the one described in #8264 (comment) using |
What's the migration path for people doing this already before 6.4 and now want to use the new data loader? In other words, how can one use |
Why is it I'm sure Remix team removed it for a reason, why drop a working feature or label it unstable unless you foresee issues in the future? Also there's @9jaGuy 's question about using this with data loader. This is a very common use case outside happy path documentation, it'd be great to get a fully supported method. |
There's the newly introduced "redirect" method to redirect from outside of the component. |
Sadly, it doesn't work this way Something like this:
|
Aaaand It's Gone! https://reactrouter.com/en/6.4.0-pre.14/routers/history-router Spent the past several days attempting to get 6.4 to work on a module federation mfe app without unstable_HistoryRouter (or unsafe_navigationcontext or using any imports from history) so I can use the new data loaders. Trying with createBrowserRouter / createMemoryRouter for dev and prod. Wondering if it's possible. Some examples and up to date docs for 6.4.2 would be nice at this point. |
Yeah, this doesn't appear to be supported in the RR roadmap going forward (no imports from 'history' since 6.0-beta) and based on what I've tried to date it hasn't worked with the new data loaders. In looking through all of the open and closed issues - this is one of the most active issues for RR right now that needs a solution inline with the roadmap. |
Is there any official method to fix this issue? My case is redirecting user to sign in page when getting SESSION_TIMEOUT error code in every single api request, and how can I make it without hacking methods? |
I knew it... Whether it's a timeout or handling errors, we need a way to navigate outside components. I've been using this feature in all large apps I've worked in for at least 5 years now (some my design, some not). Not everything is a React component, What about Redux or Sagas? I'm happy to remove history as you say in the docs, but we need a way to navigate from outside components. This is not a minor detail I can just remove and adapt. |
How to navigate out component when use createBrowserRouter? |
I am using router5 and was considering migrating my application to v6. The inability to navigate outside a component seems to disregard an enormous group of use cases and people fundamentally. I use redux-saga to manage complex flows. I know over the years, we have had new technologies emerge and other solutions to handle side effects, but with such a large application with 100+ saga files, this just isn't going to happen anytime soon for us. At least router5 had a way to plug into the redux structure and allow navigations to happen outside of react components. With this being an issue I have had to halt migrating to v6 for now until this is resolved. I will continue to use router5 which feels more robust (for my use case anyway.) This is a library (router5( that hasnt released a new version nearly 2 years. |
Found this explanation about the change in accessing history in another issue about RR upgrade from v6.3 to v6.4. Still working through this issue! |
Hey folks! I'm centralizing the info around |
There is no official solve of this issue still, why? |
React itself is the reason we discouraged navigating outside of the React tree due to UI sharding (a problem with React's concurrent mode and suspense where it would get out of sync with an external store like To unblock our work in React Router, and to make sure we were forward compatible with React, we discouraged this use case and waited for the React team to provide a solution. However, we still provided a path forward for you with It wasn't until React 18 that We have a long history of maintaining our old versions. After 8+ years we've only had two breaking releases. Even v3 still gets patches. You can stay on v5 if things in v6 aren't worth the development effort to upgrade. It will be maintained. If the features in v6 look interesting to you, Matt has outlined some options in the other issue. |
Conversation is locked so that further questions happen on the other issue :) |
Also, if you're on |
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 useuseHistory()
hook. However I am not sure how we can do the navigation outside React context in v6, cause in v5, your can manually provide ahistory
object as a prop toRouter
component, and reference it in other places even it is not inside React context: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:
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:
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
The text was updated successfully, but these errors were encountered: