-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
[Bug]: useNavigate function triggers constant rerender when used in useEffect #8349
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
Comments
|
@MeiKatz actually it does not initiate an endless loop but just toggles back to "Dashboard" on every route change. That is it seems, because The strange thing is that this function useNavigate() {
!useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
// router loaded. We can help them understand how to avoid that.
`useNavigate() may be used only in the context of a <Router> component.`) : void 0;
let navigator = useContext(NavigatorContext);
let {
pathname,
basename
} = useContext(RouteContext);
let activeRef = useRef(false);
useEffect(() => {
activeRef.current = true;
});
let navigate = useCallback((to, options = {}) => {
if (activeRef.current) {
if (typeof to === "number") {
navigator.go(to);
} else {
let path = resolvePath(to, pathname, basename);
(!!options.replace ? navigator.replace : navigator.push)(path, options.state);
}
} else {
warning(false, `You should call navigate() in a useEffect, not when ` + `your component is first rendered.`) ;
}
}, [basename, navigator, pathname]);
return navigate;
} The code of export function useNavigate(): NavigateFunction {
invariant(
useInRouterContext(),
// TODO: This error is probably because they somehow have 2 versions of the
// router loaded. We can help them understand how to avoid that.
`useNavigate() may be used only in the context of a <Router> component.`
);
let { basename, navigator } = React.useContext(NavigationContext);
let { matches } = React.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
let routePathnamesJson = JSON.stringify(
matches.map(match => match.pathnameBase)
);
let activeRef = React.useRef(false);
React.useEffect(() => {
activeRef.current = true;
});
let navigate: NavigateFunction = React.useCallback(
(to: To | number, options: { replace?: boolean; state?: any } = {}) => {
warning(
activeRef.current,
`You should call navigate() in a React.useEffect(), not when ` +
`your component is first rendered.`
);
if (!activeRef.current) return;
if (typeof to === "number") {
navigator.go(to);
return;
}
let path = resolveTo(
to,
JSON.parse(routePathnamesJson),
locationPathname
);
if (basename !== "/") {
path.pathname = joinPaths([basename, path.pathname]);
}
(!!options.replace ? navigator.replace : navigator.push)(
path,
options.state
);
},
[basename, navigator, routePathnamesJson, locationPathname]
);
return navigate;
} So this hook grew and something must have happend along the way that broke this ref-equality thing and it must be somewhere between For me it is kind of essential that this function has referential equality between route changes because some things like the examples above rely on it. |
I found a (temporary?) solution for my problem that seems to work using a export const AppRouter = () => {
const { isAuthenticated } = useAuthenticatedUser();
const routes = useRoutes(APP_ROUTES);
const navigate = useNavigate();
const initialRoutesSetRef = useRef(false);
// This `useEffect` hook sets the route based on the `isAuthenticated` state
// of `authUserVar` when the app is renderd the first time.
// `authUserVar` is a reactive variable (state management from ApolloClient)
// that is initially populated with undefined values or with values from localStorage.
//
// The initial route is set to `dashboard` when the user had the `stayLoggedIn` flag set
// on the last login and therefor the login data was stored in local storage.
// If this data could not be found in localStorage the user is redirected to the `login` page.
//
// Due to some changes in the react router past 6.0.0-beta.2 the referential-equality
// of `navigate` that should be ensured by `useCallback` in `useNavigate` was not given anymore.
//
// To circumvent this drawback, the `initialRoutesSetRef` is set to `true` once the initial
// decision, where the user should be directed to, was made and therefore `navigate` is not called again.
useEffect(() => {
if (!initialRoutesSetRef.current) {
if (isAuthenticated) {
initialRoutesSetRef.current = true;
navigate(DASHBOARD.absolutePath);
} else {
initialRoutesSetRef.current = true;
navigate(LOGIN.absolutePath);
}
}
}, [isAuthenticated, navigate]);
return <Suspense fallback={<LoadingPage />}>{routes}</Suspense>; The question is if the |
It seems |
I created a small provider that prevents updates from router context for this.
And in your app — add this provider below Router
|
Or we can introduce a config variable in the useNavigate hook that returns either a navigate function for absolute or relative navigation like: const navigateAbsolute = useNavigate({ navStyle: "ABSOLUTE" })
const navigateRelative = useNavigate({ navStyle: "RELATIVE" }) One option should then be set as default to minimize the necessity of refactors in existing code. Another way could be to set the standard <BrowserRouter navStyle="ABSOLUTE" >
<Routes >
{/* uses "ABSOLUTE" */}
</ Routes >
<Routes navStyle="RELATIVE" >
{/* uses "ABSOLUTE" */}
</ Routes >
</ BrowserRouter> This |
@chrishoermann I don't think devs should have to choose between an "ABSOLUTE" nav style and buggy rendering. Why not simply fix the bug? See my comment on this duplicate issue for a bugfix: |
closing this issue in favor of #7634 where an actual solution is suggested. |
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
What version of React Router are you using?
6.0.2
Steps to Reproduce
In ReactRouter 6.0.0.beta2 the following code worked and executed only when
isAuthenticated
changed.also this snippet ran useEffect only when
activeUser.id
changed:In the current version a re-render is triggered every time
navigate()
is called somewhere in the App.This is, I assume, caused by one of the dependencies in the dependecy array of
navigate's
useCallback
. Everytime navigate is called a dependency changes and forcesuseCallback
to create a new function and in the process kill referential equality ofnavigate
which triggers theuseEffect
. This is just a wild guess because this is one of the main differences that I noticed in a quick overview ofuseNavigate
between6.0.0-beta.2
vs6.0.2
. I also checked6.0.0-beta.3
. and this behavior exists since then.Expected Behavior
When
navigate
function ofuseNavigate
is used as auseEffect
dependecy the referential-equality ofnavigate
should be ensured on every route change.Actual Behavior
The
navigate
function ofuseNavigate
triggersuseEffect
on every route change.The text was updated successfully, but these errors were encountered: