Skip to content

React TypeScript: JSX children の型エラー解決方法

問題の概要

React TypeScript を使用している際、以下のエラーに遭遇することがあります:

This JSX tag's 'children' prop expects a single child of type 'Element | undefined', but multiple children were provided.

このエラーは、コンポーネントが単一の子要素のみを受け入れるように型定義されているのに、実際には複数の子要素が渡されている場合に発生します。

エラーの原因

元のコードでは、IInputWrapperProps インターフェースで children が以下のように定義されています:

typescript
export interface IInputWrapperProps {
  // ... 他のプロパティ
  children?: JSX.Element;
}

この定義では、children は単一の JSX.Element または undefined のみを受け入れるため、複数の要素(パスワード入力フィールドと表示/非表示トグルボタン)を渡そうとすると型エラーが発生します。

解決方法

方法1: 適切な children の型定義

最も推奨される解決策は、children の型を React.ReactNode に変更することです:

typescript
export interface IInputWrapperProps {
  label?: string;
  required?: boolean;
  minimizedLabel?: boolean;
  description?: string;
  error?: string;
  wrapperStyle?: React.CSSProperties;
  children?: React.ReactNode; // 変更点
}

React.ReactNode は、以下のすべての型を含む包括的な型です:

  • JSX要素
  • 文字列
  • 数値
  • 配列(複数の要素)
  • 真偽値(true/false)
  • null/undefined

TIP

React.ReactNode は React 18 以降で推奨される children の型です。React のすべての有効な子要素をカバーします。

方法2: React.PropsWithChildren の使用

別のアプローチとして、React.PropsWithChildren ユーティリティ型を使用する方法もあります:

typescript
import React from "react";

export interface IInputWrapperProps extends React.PropsWithChildren {
  label?: string;
  required?: boolean;
  minimizedLabel?: boolean;
  description?: string;
  error?: string;
  wrapperStyle?: React.CSSProperties;
  // children は自動的に含まれる
}

方法3: フラグメントでラップする

一時的な解決策として、複数の子要素をフラグメントでラップする方法もあります:

typescript
<InputWrapper label={label} error={error} {...rest}>
  <>
    <Passwordinput
      label={label}
      type={state ? "text" : "password"}
      onChange={e => onChange(e.target.value)}
      value={value}
      error={error}
    />
    <Svg>
      <img
        onClick={() => setstate(state => !state)}
        style={{ cursor: "pointer" }}
        src={state ? eyeShow : eyeHide}
        alt="searchIcon"
      />
    </Svg>
  </>
</InputWrapper>

ただし、これは根本的な型定義の問題を解決しないため、最初の2つの方法の方が優れています。

よくある間違い

props の分割代入忘れ

コンポーネント定義時に props の分割代入を忘れると、このエラーが発生することがあります:

typescript
// 間違った書き方
export const MyComponent: FC<PropsWithChildren> = (children) => (
  <div>{children}</div>
);

// 正しい書き方
export const MyComponent: FC<PropsWithChildren> = ({ children }) => (
  <div>{children}</div>
);

コメントによる意図しない子要素

JSX内のコメントが意図せず子要素として認識されることがあります:

typescript
// 問題が発生する可能性のあるコード
<div>
  {renderTabs()}
  {/* TODO: 保留中の作業 */}
  {renderButtons()}
</div>

修正後のコード例

修正後の InputWrapper コンポーネント:

typescript
export interface IInputWrapperProps {
  label?: string;
  required?: boolean;
  minimizedLabel?: boolean;
  description?: string;
  error?: string;
  wrapperStyle?: React.CSSProperties;
  children?: React.ReactNode; // React.ReactNode を使用
}

export default ({
  label,
  error,
  description,
  children,
  required,
  wrapperStyle,
  minimizedLabel
}: IInputWrapperProps) => {
  return (
    <ErrorBoundary id="InputWrapperErrorBoundary">
      <div style={wrapperStyle}>
        <Container>
          <Label minimized={minimizedLabel}>
            {label} {required && <span style={{ color: "red" }}> *</span>}
          </Label>
          {children}
        </Container>
        {description && <Description>{description}</Description>}
        {error && <Error>{error}</Error>}
      </div>
    </ErrorBoundary>
  );
};

まとめ

React TypeScript で children 関連の型エラーが発生した場合:

  1. React.ReactNode を使用して children の型を定義する
  2. React.PropsWithChildren ユーティリティ型を活用する
  3. 複数の子要素を渡す必要がある場合は、適切な型定義を確認する
  4. コンポーネントの props 分割代入を正しく行う

これらの対策により、型安全性を保ちながら柔軟なコンポーネント設計が可能になります。