Skip to content

TypeError: Cannot read properties of undefined (reading 'map')

このエラーはJavaScript/React開発で頻繁に遭遇する問題で、配列ではない値(多くの場合はundefined)に対して.map()メソッドを呼び出そうとした際に発生します。

問題の原因

主な原因

TypeError: Cannot read properties of undefined (reading 'map')が発生する主な原因は次の通りです:

  • 配列がまだ初期化されていない(undefined状態)
  • 非同期データ取得中にデータが到着していない
  • プロパティ名の競合やタイプミス
  • APIからのデータ取得エラー

元のコードでは、コンポーネントのプロパティとして渡されるproductsと、コンポーネント外で定義されたproducts配列が同名であるため、競合が発生しています。

解決方法

1. オプショナルチェイニング (Optional Chaining) の使用

最も簡潔で現代的な解決方法は、オプショナルチェイニング演算子(?.)を使用することです:

jsx
{products?.map((product) => (
  <Grid item key={product.id} xs={12} sm={6} md={4} lg={3}>
    <Product product={product} />
  </Grid>
))}

2. 論理AND演算子による条件付きレンダリング

オプショナルチェイニングがサポートされていない環境では、論理AND演算子を使用します:

jsx
{products && products.map((product) => (
  <Grid item key={product.id} xs={12} sm={6} md={4} lg={3}>
    <Product product={product} />
  </Grid>
))}

3. フォールバック配列の提供

配列がundefinedの場合に空の配列を使用する方法:

jsx
{(products || []).map((product) => (
  <Grid item key={product.id} xs={12} sm={6} md={4} lg={3}>
    <Product product={product} />
  </Grid>
))}

4. ローディング状態の管理

非同期データ取得時のベストプラクティス:

jsx
const Products = ({ products }) => {
  const classes = useStyles();
  const [loading, setLoading] = useState(true);

  // データ取得処理(useEffect内など)
  useEffect(() => {
    const fetchData = async () => {
      try {
        // データ取得ロジック
        setLoading(false);
      } catch (error) {
        console.error('Error fetching products:', error);
        setLoading(false);
      }
    };
    
    fetchData();
  }, []);

  if (loading) {
    return <div>Loading products...</div>;
  }

  return (
    <main className={classes.content}>
      <div className={classes.toolbar} />
      <Grid container justifyContent="center" spacing={4}>
        {products?.map((product) => (
          <Grid item key={product.id} xs={12} sm={6} md={4} lg={3}>
            <Product product={product} />
          </Grid>
        ))}
      </Grid>
    </main>
  );
};

変数名の競合解決

元のコードには変数名の競合問題も存在します:

jsx
// 問題点: コンポーネント外のproductsとプロパティのproductsが競合
const products = [ /* ハードコードされたデータ */ ];

const Products = ({ products }) => { // ここで競合が発生
  // コンポーネント内ではプロパティのproductsが優先される
  return (
    {/* このproductsはプロパティを参照する */}
    {products.map((product) => ( /* ここでエラーが発生 */ ))}
  );
};

解決策としては、名前の衝突を避けるために変数名を変更します:

jsx
// ハードコードデータの名前を変更
const initialProducts = [
  {id: 1, name: 'Shoes', description: 'Running Shoes.' },
  {id: 2, name: 'MacBook', description: 'Apple MacBook.' },
];

// またはプロパティ名を変更
const Products = ({ products: productsProp }) => {
  // コンポーネント内ではproductsPropを使用
  return (
    {productsProp?.map((product) => ( /* ... */ ))}
  );
};

その他の考慮事項

ベストプラクティス

  1. プロップスのデフォルト値設定:

    jsx
    const Products = ({ products = [] }) => {
      // productsが未定義の場合、空配列がデフォルト値になる
    };
  2. 型チェックの導入:

    jsx
    import PropTypes from 'prop-types';
    
    Products.propTypes = {
      products: PropTypes.arrayOf(PropTypes.object)
    };
  3. エラーハンドリングの強化:

    jsx
    {Array.isArray(products) ? (
      products.map(/* ... */)
    ) : (
      <div>Error: products is not an array</div>
    )}

実際の修正例

元のコードを修正した完全な例:

jsx
import React from 'react';
import Grid from '@material-ui/core/Grid';
import Product from './Product/Product';
import useStyles from './styles';

const Products = ({ products = [] }) => {
  const classes = useStyles();

  return (
    <main className={classes.content}>
      <div className={classes.toolbar} />
      <Grid container justifyContent="center" spacing={4}>
        {products.map((product) => (
          <Grid item key={product.id} xs={12} sm={6} md={4} lg={3}>
            <Product product={product} />
          </Grid>
        ))}
      </Grid>
    </main>
  );
};

export default Products;

まとめ

Cannot read properties of undefined (reading 'map')エラーは、基本的に配列が未定義の状態で.map()メソッドを呼び出そうとした際に発生します。オプショナルチェイニングや条件付きレンダリングを使用することで、このエラーを効果的に防ぐことができます。また、変数名の競合を避け、適切なデフォルト値の設定と型チェックを実装することで、より堅牢なコードを作成できます。