Fixing TypeScript Error: "Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element'"
When working with React portals in TypeScript, you might encounter this common type error when trying to use document.getElementById()
with ReactDOM.createPortal()
. This article explains why this happens and presents several solutions to resolve it effectively.
Understanding the Problem
The document.getElementById()
method returns either an HTMLElement
object if the element is found, or null
if no matching element exists. TypeScript correctly types this return value as HTMLElement | null
.
However, ReactDOM.createPortal()
requires its second parameter to be strictly an Element
type, not accepting null
. This causes the TypeScript error:
Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element'.
Type 'null' is not assignable to type 'Element'.ts(2345)
Recommended Solutions
Here are the most effective ways to handle this type mismatch:
1. Null Check with Conditional Rendering
The safest approach is to check if the element exists before using it:
const portalDiv = document.getElementById('portal');
function Portal1(props) {
if (!portalDiv) {
return null; // Or a fallback UI
}
return ReactDOM.createPortal(
<div>{props.children}</div>,
portalDiv
);
}
2. Non-null Assertion Operator (!)
When you're certain the element exists in the DOM:
const portalDiv = document.getElementById('portal')!;
function Portal1(props) {
return ReactDOM.createPortal(
<div>{props.children}</div>,
portalDiv
);
}
WARNING
Use the !
operator only when you're absolutely sure the element exists. This suppresses the type error but could cause runtime errors if the element is missing.
3. Type Assertion
Explicitly tell TypeScript the type of the element:
const portalDiv = document.getElementById('portal') as HTMLElement;
function Portal1(props) {
return ReactDOM.createPortal(
<div>{props.children}</div>,
portalDiv
);
}
4. Error Throwing for Missing Elements
For a more defensive approach that provides clear error messages:
const portalDiv = document.getElementById('portal');
if (!portalDiv) {
throw new Error("The element #portal wasn't found");
}
function Portal1(props) {
return ReactDOM.createPortal(
<div>{props.children}</div>,
portalDiv // TypeScript now knows portalDiv is not null
);
}
Complete Example with React Hook
For React components, consider this pattern using useEffect
:
import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Portal1: React.FC = ({ children }) => {
const portalContainer = useRef(document.createElement('div'));
useEffect(() => {
const portalRoot = document.getElementById('portal');
if (portalRoot) {
portalRoot.appendChild(portalContainer.current);
return () => {
portalRoot.removeChild(portalContainer.current);
};
}
}, []);
return ReactDOM.createPortal(
children,
portalContainer.current
);
};
export default Portal1;
What Not to Do
DANGER
Avoid disabling strict null checks in your TypeScript configuration:
// Don't do this in tsconfig.json:
{
"compilerOptions": {
"strictNullChecks": false
}
}
This removes valuable type safety throughout your entire project and can lead to hidden bugs.
Best Practices
- Always handle the null case - Even if you're sure an element exists, defensive programming is best
- Use type assertions sparingly - They bypass TypeScript's safety features
- Consider custom hooks - Create a reusable
usePortal
hook for cleaner code - Test edge cases - Ensure your component behaves correctly when elements are missing
By implementing these solutions, you'll resolve the TypeScript error while maintaining type safety and code reliability.