How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
React · TypeScript/React

Fix Error: Target container is not a DOM element. in React

This error fires when ReactDOM.createPortal or ReactDOM.render targets a DOM element that does not exist yet. The element may be missing from the HTML, the script may run before the DOM is ready, or the portal target ID is misspelled. Fix it by ensuring the target element exists in the HTML before the portal renders, or by deferring portal rendering to useEffect.

Reading the Stack Trace

Error: Target container is not a DOM element. at createPortal (node_modules/react-dom/cjs/react-dom.development.js:254:11) at Modal (webpack-internal:///./src/components/Modal.tsx:10:10) at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:16305:18) at mountIndeterminateComponent (node_modules/react-dom/cjs/react-dom.development.js:20069:13) at beginWork (node_modules/react-dom/cjs/react-dom.development.js:21587:16) at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:4164:14) at Object.invokeGuardedCallbackDev (node_modules/react-dom/cjs/react-dom.development.js:4213:16) at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:4277:31) at beginWork$1 (node_modules/react-dom/cjs/react-dom.development.js:27451:7) at performUnitOfWork (node_modules/react-dom/cjs/react-dom.development.js:26557:12)

Here's what each line means:

Common Causes

1. Missing DOM element in HTML

The index.html file does not contain the target element that the portal tries to render into.

// index.html only has <div id="root"></div>
// Missing <div id="modal-root"></div>

export default function Modal({ children }) {
  return createPortal(
    <div className="modal">{children}</div>,
    document.getElementById('modal-root')!
  );
}

2. Typo in element ID

The ID string passed to getElementById does not match the ID in the HTML due to a typo.

// HTML has: <div id="modal-root"></div>
// Component uses wrong ID:
const target = document.getElementById('modalRoot');
return createPortal(<div>{children}</div>, target!);

3. SSR environment without DOM

During server-side rendering, document is not available, so getElementById returns null and createPortal crashes.

export default function Modal({ children }) {
  const target = document.getElementById('modal-root');
  return createPortal(<div>{children}</div>, target!);
}

The Fix

Defer the portal target lookup to useEffect, which only runs on the client after the DOM is ready. If the target element does not exist, create it dynamically. Render null until the container is available, preventing the crash entirely.

Before (broken)
export default function Modal({ children }) {
  return createPortal(
    <div className="modal">{children}</div>,
    document.getElementById('modal-root')!
  );
}
After (fixed)
import { createPortal } from 'react-dom';
import { useState, useEffect } from 'react';

export default function Modal({ children }: { children: React.ReactNode }) {
  const [container, setContainer] = useState<HTMLElement | null>(null);

  useEffect(() => {
    let el = document.getElementById('modal-root');
    if (!el) {
      el = document.createElement('div');
      el.id = 'modal-root';
      document.body.appendChild(el);
    }
    setContainer(el);
  }, []);

  if (!container) return null;

  return createPortal(
    <div className="modal">{children}</div>,
    container
  );
}

Testing the Fix

import { render, screen } from '@testing-library/react';
import Modal from './Modal';

describe('Modal', () => {
  afterEach(() => {
    const el = document.getElementById('modal-root');
    if (el) el.remove();
  });

  it('creates modal-root if missing', () => {
    render(<Modal>Hello</Modal>);
    expect(document.getElementById('modal-root')).toBeInTheDocument();
  });

  it('renders children into the portal', () => {
    render(<Modal><p>Modal content</p></Modal>);
    expect(screen.getByText('Modal content')).toBeInTheDocument();
  });

  it('does not crash when rendered on the server', () => {
    expect(() => render(<Modal>Test</Modal>)).not.toThrow();
  });
});

Run your tests:

npm test

Pushing Through CI/CD

git checkout -b fix/react-portal-target-error,git add src/components/Modal.tsx src/components/__tests__/Modal.test.tsx,git commit -m "fix: defer portal target lookup to useEffect and create if missing",git push origin fix/react-portal-target-error

Your CI config should look something like this:

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --coverage
      - run: npm run build

The Full Manual Process: 18 Steps

Here's every step you just went through to fix this one bug:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. Monitor production to confirm the error is resolved

Total time: 30-60 minutes. For one bug.

Or Let bugstack Fix It in Under 2 minutes

Every step above? bugstack does it automatically.

Step 1: Install the SDK

npm install bugstack-sdk

Step 2: Initialize

import { initBugStack } from 'bugstack-sdk'

initBugStack({ apiKey: process.env.BUGSTACK_API_KEY })

Step 3: There is no step 3.

bugstack handles everything from here:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. Deploys to production (or opens a PR for review)

Time from error to fix deployed: Under 2 minutes.

Human involvement: zero.

Try bugstack Free →

No credit card. 5-minute setup. Cancel anytime.

Deploying the Fix (Manual Path)

  1. Run the test suite locally to confirm the portal renders correctly.
  2. Open a pull request with the deferred portal target lookup.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. Merge to main and verify modals work in staging.

Frequently Asked Questions

BugStack validates that the portal target exists or is created dynamically, runs your test suite including SSR scenarios, and confirms no layout regressions before marking it safe.

BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.

Adding a static element to index.html is simpler and slightly faster. Dynamic creation is better when you cannot modify the HTML template, such as in micro-frontend or library scenarios.

Yes. Even though the portal renders outside the parent DOM hierarchy, React events still bubble through the React component tree as if the portal were inside its parent.