This error occurs when ReactDOM.render() or createRoot() tries to mount to a DOM element that does not exist or is null. Common causes include targeting the wrong element ID, loading React before the DOM is ready, or missing the target div in your HTML. Fix it by ensuring the target element exists and scripts load after the DOM.
This error happens when your React code tries to call render() on ReactDOM when the DOM element you're targeting doesn't exist or is null/undefined. In older React versions (pre-18), you would use ReactDOM.render(). In React 18+, you use ReactDOM.createRoot(). The error message specifically says "Cannot read property 'render' of null" because your code is trying to access the render method on something that is null. This typically means the target DOM element (often identified by an ID like "root") cannot be found in the HTML document. This is a fundamental React setup problem - if React cannot find a place to mount your application in the DOM, it cannot render anything. The application fails immediately, before any React components can even run. This is one of the most common issues when setting up React from scratch or when HTML and JavaScript files are misaligned.
Ensure your HTML file contains the element with the ID you're targeting. This is the most common cause:
<!-- ✅ CORRECT - target div exists -->
<!DOCTYPE html>
<html>
<head>
<title>My React App</title>
</head>
<body>
<div id="root"></div> <!-- Target element must exist! -->
<script src="app.js"></script>
</body>
</html><!-- ❌ WRONG - no target div -->
<!DOCTYPE html>
<html>
<head>
<title>My React App</title>
</head>
<body>
<!-- Missing <div id="root"></div> -->
<script src="app.js"></script>
</body>
</html>Verify:
1. Open your HTML file in a text editor
2. Search for the exact div (e.g., <div id="root"></div>)
3. Confirm the ID matches what you use in React code
Make sure the ID in HTML matches the ID you use in your JavaScript:
// ❌ WRONG - HTML has id="root" but code looks for id="app"
const root = document.getElementById('app');
ReactDOM.render(<App />, root);
// ✅ CORRECT - ID matches
const root = document.getElementById('root');
ReactDOM.render(<App />, root);For React 18+:
// ❌ WRONG ID
const root = ReactDOM.createRoot(document.getElementById('app'));
// ✅ CORRECT ID
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);Common ID mismatches:
- HTML uses id="root" but code uses getElementById('app')
- HTML uses id="container" but code uses getElementById('root')
- Typo in either HTML or JavaScript (e.g., "rott" vs "root")
Place your script tag at the end of the body to ensure the DOM is parsed first:
<!-- ✅ CORRECT - script at end of body -->
<!DOCTYPE html>
<html>
<head>
<title>My React App</title>
</head>
<body>
<div id="root"></div>
<!-- Script loads after DOM is ready -->
<script src="index.js"></script>
</body>
</html><!-- ⚠️ OLDER PATTERN - script in head (works but less ideal) -->
<!DOCTYPE html>
<html>
<head>
<title>My React App</title>
<!-- Use defer attribute so it runs after DOM is ready -->
<script defer src="index.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>If you must put scripts in the head, use the defer attribute:
<script defer src="index.js"></script>Without defer, the script runs before the DOM is parsed, and document.getElementById() returns null.
Defensive coding: verify the element exists before trying to render:
// ✅ Check if element exists
const rootElement = document.getElementById('root');
if (!rootElement) {
console.error('Root element not found. Check your HTML and ID.');
process.exit(1);
}
ReactDOM.render(<App />, rootElement);For React 18+:
// ✅ REACT 18 - Safe pattern
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Root element with id "root" not found in HTML');
}
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);Or using optional chaining and nullish coalescing:
// ✅ Alternative with defensive operators
const root = document.getElementById('root');
if (root) {
ReactDOM.render(<App />, root);
} else {
console.error('Could not find root element. Verify id="root" exists in HTML.');
}This gives you a clear error message for debugging.
If you're using a build tool (Webpack, Vite, Next.js), ensure it's serving the correct HTML:
For Vite projects:
src/
├── index.html ← Vite looks for this in src/
├── main.tsx
└── App.tsxFor Create React App:
public/
├── index.html ← Must have <div id="root"></div>
└── favicon.ico
src/
├── index.js
├── App.jsFor Next.js:
- Next.js handles this automatically, but if using custom _document.tsx, ensure root div exists
Check your build output:
1. In development, open DevTools → Elements tab
2. Search for your target div (id="root")
3. If it's not there, the HTML file isn't being served correctly
For production builds, check the generated HTML in your build folder:
# Example for Create React App
cat build/index.html | grep "root"
# Example for Vite
cat dist/index.html | grep "root"If the root element is created dynamically, ensure it exists before React renders:
// ❌ WRONG - root might not exist yet when render() runs
document.body.innerHTML += '<div id="root"></div>';
ReactDOM.render(<App />, document.getElementById('root'));
// ✅ CORRECT - create element, then render
const root = document.createElement('div');
root.id = 'root';
document.body.appendChild(root);
// Now safe to render
ReactDOM.render(<App />, document.getElementById('root'));Better pattern using useEffect in a wrapper:
// ✅ Create root container on mount
function initializeApp() {
let rootElement = document.getElementById('root');
if (!rootElement) {
rootElement = document.createElement('div');
rootElement.id = 'root';
document.body.appendChild(rootElement);
}
ReactDOM.render(<App />, rootElement);
}
// Wait for DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeApp);
} else {
initializeApp();
}Add debugging to understand what's happening:
console.log('Looking for element with id="root"');
const rootElement = document.getElementById('root');
console.log('Found element:', rootElement);
if (rootElement === null) {
console.error('Root element not found!');
console.log('Available elements:', document.querySelectorAll('[id]'));
process.exit(1);
}
console.log('Root element:', rootElement);
ReactDOM.render(<App />, rootElement);Or check what elements exist:
// List all elements with IDs
const allElements = document.querySelectorAll('[id]');
console.log('All elements with IDs:',
Array.from(allElements).map(el => ({
id: el.id,
tag: el.tagName,
html: el.outerHTML.substring(0, 100)
}))
);Check DevTools:
1. Open browser console
2. Type: document.getElementById('root')
3. If it returns null, the element doesn't exist
4. Check Elements tab to see actual DOM structure
TypeScript can catch some of these issues:
// React 18+ with TypeScript
import { ReactElement } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
// TypeScript ensures rootElement exists before using it
if (!rootElement) {
throw new Error(
'Root element not found. Ensure <div id="root"></div> exists in your HTML.'
);
}
const root = ReactDOM.createRoot(rootElement as HTMLElement);
root.render(<App />);Or with stricter null checking:
const getRootElement = (): HTMLElement => {
const root = document.getElementById('root');
if (!root) {
throw new Error(
'Root element not found in HTML. Expected: <div id="root"></div>'
);
}
return root;
};
const root = ReactDOM.createRoot(getRootElement());
root.render(<App />);Enable strict null checking in tsconfig.json:
{
"compilerOptions": {
"strictNullChecks": true,
"strict": true
}
}React Version Differences: React 16-17 use ReactDOM.render(), while React 18+ uses ReactDOM.createRoot(). Both require the target DOM element to exist. If upgrading to React 18, update your initialization code accordingly.
Build Tool Differences: Create React App automatically creates an index.html with <div id="root"></div>, but Vite and other tools may require you to create it. Check your build tool's documentation for the expected HTML structure.
Production vs Development: Sometimes the HTML is correct in development but missing in production if the build process doesn't copy the HTML file correctly. Always verify the final build output contains the correct target element.
Browser Console Timing: If you're testing in the browser console (e.g., pasting React code directly), the DOM might not have the expected structure. Use a proper HTML file and script setup instead.
Multiple Root Elements: Modern React applications can have multiple root elements. If you need multiple React apps on one page, create multiple divs with different IDs and initialize each separately.
NextJS and Frameworks: Next.js, Remix, and similar frameworks handle DOM initialization automatically. If using these frameworks, you generally don't need to manually call ReactDOM.render() - the framework does it for you.
Shadow DOM: If your React app is in a Shadow DOM (Web Components), document.getElementById() might not find elements in the Shadow root. Use shadowRoot.getElementById() or query within the correct document context.
SSR Considerations: Server-side rendering (SSR) uses hydrate() instead of render(). The HTML generated on the server and sent to the browser must contain the target element with matching content structure.
Prop spreading could cause security issues
Prop spreading could cause security issues
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
React.FC expects children prop to be defined
React.FC no longer includes implicit children prop