You’re running into a classic SPA problem—history navigation (navigate(-n)
) in React Router is async, so if you immediately try to do a “replace” after going back, it’ll probably happen too soon and mess up your navigation stack.
I’ve hit this myself, and yeah, stuff like timeouts and session storage might seem to work, but they’re hacky and break easily, especially in production or different browsers.
Let React Router’s location
tell you when the navigation is done, then do your replace.
This way you don’t need to guess, wait, or stash state anywhere weird.
Drop-in Hook (for React Router v6/v7):
import { useNavigate, useLocation } from "react-router-dom";
import { useState, useEffect } from "react";
// Usage: const backAndReplace = useAtomicBackReplace();
// backAndReplace(-3, "/new-route");
function useAtomicBackReplace() {
const navigate = useNavigate();
const location = useLocation();
const [pending, setPending] = useState(null);
function backAndReplace(steps, to) {
setPending({ steps, to, from: location.pathname });
navigate(steps);
}
useEffect(() => {
if (pending && location.pathname !== pending.from) {
navigate(pending.to, { replace: true });
setPending(null);
}
}, [location, pending, navigate]);
return backAndReplace;
}
How do you use it?
Just call backAndReplace(-3, "/your-new-route")
wherever you’d normally want to do the navigation+replace.
Because:
Browsers and environments differ in timing, so setTimeouts can fail randomly.
Session storage is overkill for something that’s just local UI navigation.
This “listens” for the navigation to finish, so you don’t need to guess.
Real-world usage:
const backAndReplace = useAtomicBackReplace();
// Example: Go back 3 steps, then replace that entry
backAndReplace(-3, "/new-route");