skip to Main Content

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


  1. Chosen as BEST ANSWER

    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,

    const MemoizedRoute = React.memo(({ isPrivate, component: Component, ...rest }) => {
    if (isPrivate) {
      return (
        <PrivateRoute
          {...rest}
          component={Component} 
        />
      );
    }
    return (
      <Route
        {...rest}
        render={routeProps => <Component {...routeProps} {...rest} />}
      />
    );
    });
    

    I modified my loadable component to cache the LazyComponent correctly as suggested by @Shehan,

    const cachedComponentsMap = new Map();
    
    const useCachedLazy = (importFunc, namedExport) => {
      try {
        if (!cachedComponentsMap.has(importFunc)) {
          const LazyComponent = lazy(() =>
            importFunc().then(module => ({
              default: namedExport ? module[namedExport] : module.default,
            }))
          );
          cachedComponentsMap.set(importFunc, LazyComponent);
        }
        return cachedComponentsMap.get(importFunc);
      } catch (error) {
        console.error('Error loading component:', error);
        throw error; 
      }
    };
    
    
    const loadable = (importFunc, { namedExport = null } = {}) => {
      const cachedKey = `${importFunc}-${namedExport}`;
    
      if (!cachedComponentsMap.has(cachedKey)) {
        const MemoizedLazyComponent = React.memo(props => {
          const LazyComponent = useCachedLazy(importFunc, namedExport);
          return <LazyComponent {...props} />;
        });
    
        cachedComponentsMap.set(cachedKey, MemoizedLazyComponent);
      }
    
      return cachedComponentsMap.get(cachedKey);
    };
    

    Now the components do not unmount and remount unnecessary and I also got lazy working and the performance is improved.


  2. There are several fixes;

    1. Use a Stable Reference for LazyComponent
      Avoid creating a new LazyComponent for each render. Instead, cache the LazyComponent instances in a way that persists between renders, similar to what you’ve done with useCachedLazy.
    const cachedComponentsMap = new Map();
    
    const loadable = (importFunc, { namedExport = null } = {}) => {
      const cachedKey = `${importFunc}-${namedExport}`;
    
      if (!cachedComponentsMap.has(cachedKey)) {
        const MemoizedLazyComponent = React.memo(props => {
          const LazyComponent = useCachedLazy(importFunc, namedExport);
          return <LazyComponent {...props} />;
        });
    
        cachedComponentsMap.set(cachedKey, MemoizedLazyComponent);
      }
    
      return cachedComponentsMap.get(cachedKey);
    };
    
    1. 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.

    2. Use React.memo on Route-Level Components
      consider wrapping PrivateRoute or Route components with React.memo to prevent unnecessary re-rendering or remounting due to prop changes.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search