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

[Bug]: Outlet component remounts in production when changing state above it

Open mindnektar opened this issue 10 months ago • 2 comments

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.

mindnektar avatar Apr 17 '24 17:04 mindnektar

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.

mindnektar avatar Apr 18 '24 07:04 mindnektar

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.

timdorr avatar Apr 19 '24 02:04 timdorr

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.

mindnektar avatar May 30 '24 15:05 mindnektar