react-router icon indicating copy to clipboard operation
react-router copied to clipboard

[Bug]: [v6] useNavigate() doesn't navigate to the same route, but with different param

Open Maqsim opened this issue 2 years ago • 12 comments

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

Maqsim avatar Nov 05 '21 22:11 Maqsim

I also have same errors ? But How we can resolve this?

ahmedjsofficial avatar Nov 06 '21 18:11 ahmedjsofficial

@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

Maqsim avatar Nov 06 '21 19:11 Maqsim

@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

Joker-Bat avatar Nov 07 '21 09:11 Joker-Bat

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

Maqsim avatar Nov 07 '21 22:11 Maqsim

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 avatar Nov 08 '21 02:11 Joker-Bat

@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)

Maqsim avatar Nov 08 '21 04:11 Maqsim

@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.

Joker-Bat avatar Nov 08 '21 05:11 Joker-Bat

I also have the same error in a electron app with MemoryRouter. But it works well in the development mode.

dengzikun avatar Dec 08 '21 02:12 dengzikun

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.

lensbart avatar Mar 02 '22 21:03 lensbart

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...

allemas avatar Apr 13 '22 09:04 allemas

Is there any progress on this issue?

Galileo01 avatar Apr 17 '22 11:04 Galileo01

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

Maqsim avatar Apr 17 '22 13:04 Maqsim