Skip to content

React Portalで発生する「Type 'null' is not assignable to type 'Element'」エラーの解決方法

問題の概要

TypeScriptでReact Portalを使用する際、以下のようなエラーが発生することがあります:

Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element'.
Type 'null' is not assignable to type 'Element'.ts(2345)

このエラーは、document.getElementById()が要素を返すかnullを返す可能性があるのに対し、ReactDOM.createPortal()nullを受け入れないことに起因しています。

原因分析

getElementById()メソッドの戻り値の型はHTMLElement | nullです。これは、指定したIDを持つ要素が見つからない場合にnullを返す可能性があるためです。一方、ReactDOM.createPortal()の第2引数はElement型のみを受け入れるため、nullが渡されるとTypeScriptコンパイラがエラーを報告します。

html
<!-- index.html -->
<body>
    <div id="portal"></div>
    <div id="root"></div>
</body>
typescript
// 問題のあるコード
const portalDiv = document.getElementById('portal');
// portalDiv: HTMLElement | null

function Portal1(props) {
  return ReactDOM.createPortal(
    <div>{props.children}</div>, 
    portalDiv // エラー発生箇所
  );
}

解決方法

方法1: 型アサーションを使用する (推奨)

要素が確実に存在することがわかっている場合、型アサーションを使用してTypeScriptに型を明示できます:

typescript
const portalDiv = document.getElementById('portal') as HTMLElement;
// または
const portalDiv = document.getElementById('portal')!;

INFO

!演算子(non-null assertion operator)は、値がnullまたはundefinedではないことをTypeScriptに伝えます。ただし、実際に要素が存在することを確認している場合にのみ使用してください。

方法2: nullチェックを行う

より安全な方法として、要素の存在を確認してから使用します:

typescript
const portalDiv = document.getElementById('portal');

if (!portalDiv) {
    throw new Error("要素 #portal が見つかりませんでした");
}

// この時点でTypeScriptはportalDivがHTMLElementであると認識する

方法3: 条件付きレンダリング

Portalの作成前に要素の存在をチェックする方法もあります:

typescript
function Portal1({ children }) {
  const portalDiv = document.getElementById('portal');
  
  return portalDiv 
    ? ReactDOM.createPortal(<>{children}</>, portalDiv) 
    : null;
}

方法4: useEffect内で要素を取得する

Reactコンポーネント内でPortalを使用する場合、よりReactらしいアプローチ:

typescript
import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';

const ModalPortlet: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const portalContainer = useRef(document.createElement('div'));
  const portalRoot = useRef(document.getElementById('portal'));

  useEffect(() => {
    if (!portalRoot.current) return;
    
    portalRoot.current.appendChild(portalContainer.current);
    
    return () => {
      if (portalRoot.current) {
        portalRoot.current.removeChild(portalContainer.current);
      }
    };
  }, []);

  return ReactDOM.createPortal(children, portalContainer.current);
};

export default ModalPortlet;

ベストプラクティス

TIP

  1. 型安全性を優先する: asアサーションを使用する場合は、要素の存在が保証されている場合に限る
  2. エラーハンドリングを実装する: 要素が見つからない場合のフォールバック処理を検討する
  3. コンポーネントのライフサイクルを考慮する: useEffectを使用して適切なタイミングで要素の追加・削除を行う
  4. テストを書く: Portalを使用するコンポーネントのテスト時に、要素の存在確認をモックする

WARNING

strictNullChecks: falseに設定する方法は非推奨です。この設定を無効にすると、コード全体の型安全性が損なわれ、潜在的なnull参照エラーを見逃す可能性があります。

まとめ

React PortalでTypeScriptの型エラーが発生した場合、主に以下の解決方法があります:

  1. 型アサーション (as HTMLElement または !) を使用する
  2. nullチェックを行って型を絞り込む
  3. 条件付きレンダリングで安全に処理する
  4. Reactのライフサイクルを活用した実装を行う

アプリケーションの要件と安全性のバランスを考慮し、適切な方法を選択してください。特に大規模なプロジェクトでは、型安全性を保ちながら明確なエラーハンドリングを行う方法2や方法4が推奨されます。