I have created a lodable component,
const componentsMap = new Map();
const useCachedLazy = (importFunc, namedExport) => {
if (!componentsMap.has(importFunc)) {
const LazyComponent = lazy(() =>
importFunc().then(module => ({
default: namedExport ? module[namedExport] : module.default,
}))
);
componentsMap.set(importFunc, LazyComponent);
}
return componentsMap.get(importFunc);
};
const loadable = (importFunc, { namedExport = null } = {}) => {
const MemoizedLazyComponent = React.memo(props => {
const LazyComponent = useCachedLazy(importFunc, namedExport);
return (
<LazyComponent {...props} />
);
});
return MemoizedLazyComponent;
};
export default loadable;
You can see that I have tried caching the lazy component here using memo and useCachedLazy.
My routes are called in App.js like this:
<Suspense fallback={<div><Spin /></div>}>
<Switch>
{routes.map(route => {
const LazyComponent = loadable(
() => Promise.resolve({ default: route.component }),
{ namedExport: route.namedExport } // passing named exports to loadable
);
return route.auth ? (
<PrivateRoute
{...route}
key={route.key}
collapsed={collapsed}
toggleCollapsed={toggleCollapsed}
showHiddenTriggerKeyPress={showHiddenTriggerKeyPress}
component={LazyComponent}
locale={locale}
/>
) : (
<Route {...route} key={route.key} component={LazyComponent} />
);
})}
</Switch>
</Suspense>
in routes array, I pass the component directly in the prop route.component.
Now lazy loading has really improved loading speed for me, so this is a good thing. But whenever something changes, all components are remounting instead they should be re-rendered. Please mention any probable fixes for this.
EDIT
I didn’t mention an example previously on how these components are remounting. So here’s an example, I have setup a key, shift+X in my app.js which I use to show/hide some hidden items in my components.
const shiftXKeyPress = useKeyPress('X');
const showHiddenTriggerKeyPress = shiftXKeyPress;
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
let prevKey = '';
function downHandler({ key }) {
if (prevKey === targetKey) return;
if (key === targetKey) {
setKeyPressed(!keyPressed);
prevKey = targetKey;
}
}
useEffect(() => {
window.addEventListener('keydown', downHandler);
return () => {
window.removeEventListener('keydown', downHandler);
};
}, [keyPressed]);
return keyPressed;
}
I have passed showHiddenTriggerKeyPress as a prop to all components, see in the above code for App.js. So whenever I press shift+x it should just re-render and display related contents, instead the whole component is reloading.
2
Answers
This will be a long answer. So I made some of my custom configurations to fix this, also took help from the answer by @ShehanLakshita.
Firstly I wrapped PrivateRoute and Route with memo to prevent unnecessary re renders,
I modified my loadable component to cache the LazyComponent correctly as suggested by @Shehan,
Now the components do not unmount and remount unnecessary and I also got lazy working and the performance is improved.
There are several fixes;
LazyComponent
Avoid creating a new
LazyComponent
for each render. Instead, cache theLazyComponent
instances in a way that persists between renders, similar to what you’ve done withuseCachedLazy
.Stable Keys in Routes
Ensure that each route’s key is stable and tied to the route itself. If
key
changes dynamically, React will unmount and remount the component.Use
React.memo
on Route-Level Componentsconsider wrapping
PrivateRoute
or Route components withReact.memo
to prevent unnecessary re-rendering or remounting due to prop changes.