reactjstypescriptjsxreact-portal

Unable to insert an html element using createPortal method


I have a portal component that is outside react component tree and I'm using createPortal method from react dom to insert a Dom element inside the body element. The insertion is not working as expected.

Stackblitz link

main.tsx

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

App.tsx

import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';

function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <div>
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  );
}

export default App;

dupe.tsx

// Trying to insert H1 tag with createPortal method
import { createPortal } from 'react-dom';
import './App.css';

export function Portal() {
  return (
    <>
      <h1>Hello!</h1>
      {createPortal(
        <h1>Heading</h1>,
        document.getElementById('modal') as HTMLElement
      )}
    </>
  );
}

export default Portal;
// The div modal id here
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
  <div id="modal"></div>
</body>

Where am I Wrong?


Solution

  • You have to also render the component that creates and renders the React portal.

    Example: In your sandbox import Portal and render it in App.

    App.tsx

    import { useState } from 'react';
    import reactLogo from './assets/react.svg';
    import viteLogo from '/vite.svg';
    import './App.css';
    import Portal from './dupe'; // <-- import Portal component
    
    function App() {
      const [count, setCount] = useState(0);
    
      return (
        <>
          <div>
            <a href="https://vite.dev" target="_blank">
              <img src={viteLogo} className="logo" alt="Vite logo" />
            </a>
            <a href="https://react.dev" target="_blank">
              <img src={reactLogo} className="logo react" alt="React logo" />
            </a>
          </div>
          <h1>Vite + React</h1>
          <div className="card">
            <button onClick={() => setCount((count) => count + 1)}>
              count is {count}
            </button>
            <p>
              Edit <code>src/App.tsx</code> and save to test HMR
            </p>
          </div>
          <p className="read-the-docs">
            Click on the Vite and React logos to learn more
          </p>
    
          <Portal /> {/* <-- Render Portal Component */}
        </>
      );
    }
    
    export default App;
    

    screenshot