react-router: useRouterMatch returns new object on each render

Version

5.1.2

Test Case

https://codesandbox.io/s/eloquent-wave-vlixh

Steps to reproduce

CodeSanbox has a reproducible case

Expected Behavior

useRouteMatch hook returns a match object, when combined with a useEffect it is expected that the effect will run only when the window location or path parameters change

Actual Behavior

useRouteMatch hook returns a match object, when combined with a useEffect with the match as dependency the effect is triggered on every state change. The codesandbox example performs a state update on a timeout and logs match whenever it is changed. The logs show that on every update match has changed even if the path has not. Memoizing the match object on window.location.pathname prevents this and has the expected behaviour

This line in the hook returns the result of matchPath which creates a new object every time it is executed. Possible solution would be to do something like

return path ? 
  useMemo(() => matchPath(location.pathname, path), [location.pathname, path]) :
  match

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 22
  • Comments: 30 (7 by maintainers)

Most upvoted comments

The update is that I’ll be reviewing in the next week or two. This one is on the radar.

That’s now released in 6.1.0! 😃

Same for other hooks like useParams. It triggers a rerender on every route change, even if there are no params in url. So basically using hooks = trigger app rerender on every route change.

Maybe passing props you want to subscribe to into this hooks may help.

I face the same problem with useRouteMatch today what is the status of this issue ?

I think that makes sense. Can you submit a PR?

Is there any update on the PR? I can see all the check have passed, but there looks like no activity after April

I am still having this issue, is there a update on it?

I’ve opened up a PR to start a discussion on how this issue could be resolved in https://github.com/ReactTraining/react-router/pull/7822.

This is causing all kinds of unnecessary re-rendering, we’ve had to write our own set of wrapper functions to compensate

Nested routes trigger the infinite re-render loop. Would be great to memoize the hooks +1

If you want to perform side-effects on location change, then I think you can prob just rely on location:

const location = useLocation();
const match = useMatch();
useEffect(() => {
  // The location has changed
  nonIdempotentSideEffect(match.params.foo);
}, [location])

Or I think you could make match stable in user land like this:

const useStableRouteMatch = () => {
  const _match = useRouteMatch();
  const location = useLocation();
  const [match, setMatch] = useState(_match);
  useEffect(() => {
    setMatch(_match);
  }, [location]);
  return match;
};

// ...

const match = useStableRouteMatch();
useEffect(() => {
  // The location has changed
  nonIdempotentSideEffect(match.params.foo);
}, [match]);

However, I don’t think using React.useMemo would be safe unless you were merely trying to improve render performance as there’s no guarantee React won’t just throw away the memo’d value, e.g. don’t do this:

const useMemoRouteMatch = () => {
  const match = useRouteMatch();
  const location = useLocation();
  return useMemo(() => match, [location]);
}

// ...

const match = useMemoRouteMatch();
useEffect(() => {
  // The location has changed *or react has discarded the memo'd match value*
  nonIdempotentSideEffect(match.params.foo);
}, [match]);

A bit off topic, but simply using useLocation inside a memo’ed component will cause a rerendering if any Route above the component rerenders: https://codesandbox.io/s/festive-greider-8did7

The location value isn’t changed so it is trival to work-around, but quite a gothca IMO. https://github.com/facebook/react/issues/15156#issuecomment-474590693 for a related discussion.

@LoiKos Sorry, I have just noticed that my code was not complete. I was calling history.replace to reset the location state.

So I have updated my code. @LoiKos Can you managed to reproduce the infinite loop ?