skip to Main Content

React version: 18.3.1

Steps To Reproduce

Create a component that renders the children inside a , but only after it has obtained reference to that div (via putting the div node into a state)
Pass a lazy loaded component as children

So basically something like:

import { Suspense, lazy, useState } from 'react';

const LazyLoadedComponent = lazy(
  () =>
    new Promise((resolve) => {
      setTimeout(
        () =>
          resolve({
            default: () => <div>Lazy loaded component</div>,
          }),
        500,
      );
    }),
);

const RenderAfterMount = (props) => {
  const [node, setNode] = useState(null);

  return <div ref={setNode}>{node && props.children}</div>;
};

export const App = () => (
  <Suspense>
    <RenderAfterMount>
      <LazyLoadedComponent />
    </RenderAfterMount>
  </Suspense>
);

Link to code example:

https://stackblitz.com/edit/vitejs-vite-uqnpwm?file=src%2FApp.jsx

The current behavior

Runtime error due to an infinite loop.

The expected behavior

The lazy loaded component is rendered.

2

Answers


  1. You can can capture the reference of the node using const ref = useRef() but I’m not understanding your use case here the content inside of Suspense is considered as a single unit so even if one component will take time all the other will be rendered once the one taking time resolves, in short there is no need for you to add RenderAfterMount it will just work fine

    
    import { Suspense, lazy, useRef, useState } from 'react';
    import './styles.css';
    
    const LazyLoadedComponent = lazy(
      () =>
        new Promise((resolve) => {
          setTimeout(
            () =>
              resolve({
                default: () => (
                  <div style={{ background: 'green' }}>Lazy loaded component</div>
                ),
              }),
            5000
          );
        })
    );
    
    export default function App() {
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <Suspense>
            <SomeRandomComponent/>
              <LazyLoadedComponent />
          </Suspense>
        </div>
      );
    }
    
    const SomeRandomComponent = (props) => {
      return <div>rendering</div>
    };
    
    
    

    in this example both the text rendering and the actual lazy loaded component will be rendered after 5 seconds

    Login or Signup to reply.
  2. The proper hook to use in this case is useRef. In your example the setNode function is, well, setting the state (value) for the node constant during the rendering; that process re-renders the component thus generating the infinite loop.

    The following code uses useRef hook and validates that the ref is set (node.current) before rendering the children.

    import { Suspense, lazy, useState, useRef } from 'react';
    import './styles.css';
    
    const LazyLoadedComponent = lazy(
      () =>
        new Promise((resolve) => {
          setTimeout(
            () =>
              resolve({
                default: () => (
                  <div style={{ background: 'green' }}>Lazy loaded component</div>
                ),
              }),
            500
          );
        })
    );
    
    export default function App() {
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <Suspense>
            <RenderAfterMount>
              <LazyLoadedComponent />
            </RenderAfterMount>
          </Suspense>
        </div>
      );
    }
    
    const RenderAfterMount = (props) => {
      const node = useRef(null);
    
      return <div ref={node}>{node?.current && props.children}</div>;
    };
    

    Hope this helps you understand and solve the problem.

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