react-router
react-router copied to clipboard
[Bug]: [v6] useNavigate() doesn't navigate to the same route, but with different param
What version of React Router are you using?
v6
Steps to Reproduce
- Use HashRouter
- Define routes
<Routes>
<Route path="/" element={<QuestionsPage />} />
<Route path="/questions/:id" element={<QuestionDetailsPage />} />
</Routes>
- Go to some page
/questions/abc
- On some button click (for example) call navigate('/questions/123')
Expected Behavior
Changes the location to '#/questions/123' and re-renders the component
Actual Behavior
Changes the location to '#/questions/123', but DOESN'T re-renders the component
So technically the location (hash in my case) changes as expected, but the page component doesn't re-render as I would expect
Here is an example of the component where I am using it (electron app):
export function App() {
const navigate = useNavigate();
useEffect(() => {
document.addEventListener('paste', (e) => {
const clipboardData = e.clipboardData;
const pastedText = clipboardData?.getData('text');
if (pastedText && pastedText.startsWith('https://stackoverflow.com/questions/')) {
const questionId = pastedText.replace('https://stackoverflow.com/questions/', '').split('/')[0];
navigate(`/questions/${questionId}`);
}
});
}, []);
return (
<UserProvider LoadingComponent={<AppSpinner />}>
<Layout />
</UserProvider>
);
}
The code itself works as expected when current location IS NOT /questions/{id}
. But when I am already on a question page it stops working
I also have same errors ? But How we can resolve this?
@jsstackdevelopers Right now I have no a workaround. Currently I skipped the feature as it is not really important for me and hope this will be fixed soon
@jsstackdevelopers Right now I have no a workaround. Currently I skipped the feature as it is not really important for me and hope this will be fixed soon
Try adding location.pathname or your dynamic parameter :id as dependecy to useEffect,
const location = useLocation()
const { id } = useParams()
useEffect(() => {
... your stuff
}, [ id ]) // or location.pathname
No effect. Components doesn't re-render at all there are no chances to call useEffect
What I also noticed is that when you execute in the console history.back()
URL changes, but no re-renders
No effect. Components doesn't re-render at all there are no chances to call useEffect
What I also noticed is that when you execute in the console
history.back()
URL changes, but no re-renders
I tried the code with same scenario, as you mentioned above, with hash router,
When you already on page with some dynamic id, and if you only change that unique id, then whole page will not render again, it will render only the changes, so the useEffect with [ ] also not been called. if you want to rerender the whole page again then you should do it forcefully, or if your changes are from useEffect then add a location dependency to that. with simple text in a page with dynamic id working well.
@Joker-Bat How is that applicable to the scenario? I do not change param in the component itself I do use useNavigate outside the component. I don't think it is the right use case to have useEffect with dependency, because there can be stuff outside that. What if I don't have any code to run inside useEffect? For example if I want to render useParam() value.
I believe router should re-render whole component when any param is changed (actually like it was before v6)
@Joker-Bat How is that applicable to the scenario? I do not change param in the component itself I do use useNavigate outside the component. I don't think it is the right use case to have useEffect with dependency, because there can be stuff outside that. What if I don't have any code to run inside useEffect? For example if I want to render useParam() value.
I believe router should re-render whole component when any param is changed (actually like it was before v6)
Lets try this
In your App component add two buttons which will change that dynamic parameter :id, and in that component add one useEffect with empty dependency inside just console.log("Called")
useEffect(() => {
console.log("Called")
}, [])
try hard reload that page, and also toggle that id by click on buttons, see when that "Called" is printed on console.
I also have the same error in a electron app with MemoryRouter. But it works well in the development mode.
I’m having a similar issue with the following setup
const Button = forwardRef<HTMLAnchorElement, Props>(
(_, ref) => {
const location = useLocation()
const [mode, book] = useMemo(
() => location.pathname.split('/').filter(Boolean),
[location.pathname]
)
const button = (
<Button
ref={ref}
href={`/${mode === 'settings' ? 'book' : 'settings'}/${book}`}
icon={<FontAwesomeIcon icon={mode === 'settings' ? faEye : faCog} />}
label={
mode === 'settings' ? (
<FormattedMessage defaultMessage="View" />
) : (
<FormattedMessage defaultMessage="Settings" />
)
}
/>
)
return (
<Routes>
<Route element={button} path="/book/:book/*" />
<Route element={button} path="/settings/:book/*" />
</Routes>
)
return null
}
)
SettingsButton.displayName = 'Button'
When clicking this button, one would expect to toggle between two pages (and the icon to change), but the button
button seems memoized and doesn’t rerender.
I have the same behavior in React router V5.3 without hash mode :/ The hook UseParams is never updated so I can't subscribe to the changes in a useEffect.
I don't know if that will help but with useHistory and the history.location.pathname I can subscribe to the changes in a useEffect. Not the best solution but...
Is there any progress on this issue?
Actually @Joker-Bat's snippet worked for me.
const location = useLocation();
const { id } = useParams();
const postType: 'answer' | 'question' = (location.state && location.state.postType) || 'question';
// If answer, obtain questionId and reload
useEffect(() => {
if (!id || postType !== 'answer') {
return;
}
setIsLoaded(false);
(async () => {
const questionId = ((await stackoverflow.get(`answers/${id}`, { filter: '!-)QWsbcLyRoQ' })) as any).items[0] .question_id;
navigate(`/questions/${questionId}`, { replace: true });
})();
}, [id, postType]);
you can use useParams
hook or useLocation
So basically this issue can be close and actually it was designed RIGH, I just misunderstood it. Component should not be re-rendered on param change