react-router
react-router copied to clipboard
[Bug]: Outlet component remounts in production when changing state above it
What version of React Router are you using?
6.22.1
Steps to Reproduce
I really, really wish I could offer a simple reproduction, but I can't, since this only happens in our production build and cannot be reproduced locally. Here's a very reduced pseudo-setup:
const App = () => {
const [state, setState] = useState(0);
useEffect(() => {
window.setTimeout(() => {
setState((previous) => previous + 1);
}, 1000);
}, []);
return (
<Outlet />
);
};
const root = createRoot(document.getElementById('app'));
const router = createHashRouter([{ element: <App />, children: [...] }]);
root.render(<RouterProvider router={router} />);
The above App
component rerenders once per second. In development, everything is fine and the sub tree within Outlet
is not rerendered or remounted. In production, however, the Outlet
component is remounted, and the entire sub tree is recreated. Through debugging I have determined the Outlet
component to be the one up highest in the tree that is being remounted. App
is not remounted, as one would expect.
The crazy thing is: If I replace the Outlet
component with useOutlet
(which is exactly what Outlet
is doing internally), it works, and no remounting is happening. I have no idea how this is possible, and I have not seen any other issues related to this in the bug tracker.
Using useOutlet
is a workaround I can live with, but I would really like to understand what is happening and if this is an issue with react-router
. Do you have any clue what the problem might be without me having to show more of my setup? I can do that of course, but it's a lot because it's a big project, and there's no telling what might be relevant and what might not.
I'd be thankful for any help.
Expected Behavior
The Outlet
component does not remount.
Actual Behavior
The Outlet
component remounts.
Little follow-up: It also works completely fine if I create my own Outlet
component that looks exactly like the one within react-router
. As soon as I go back to the react-router
one, though, the remounting happens again in production. It's nuts.
This really sounds like something with your build environment that's transforming or affecting the library code to produce unexpected results. I'd go check the build output to see what code is actually being run and if it's got something in there that would change these results.
Holy moly, I found out what was going on while investigating another issue with react-router
. I was getting the error TypeError: Right side of assignment cannot be destructured
, but only in production and only in Safari. In the minified code, all I could make out was that it appeared to be thrown somewhere within react-router
. So in order to better find out exactly where it happened, I deployed an unminified version to our staging system, and lo and behold: The error was gone. Putting 1 and 1 together, I removed my custom Outlet
component, deployed it, and yep, no more remounting. So @timdorr was right, something weird was going on with the minifier. I was using uglifyjs-webpack-plugin
, but apparently it is not really compatible with webpack 5 anymore, which I migrated to recently. Bit of a shame that I had to find out this way rather than via an incompatibility warning, but it is what it is. I replaced it with terser-webpack-plugin
, and now everything is fine.
TL;DR: Don't use uglifyjs-webpack-plugin
in conjunction with webpack 5 or you'll have a bad time.