Reactでユーザ定義のコンポーネントを使う場合は名前の先頭を大文字としよう

2020.08.16

こんにちは、CX事業本部の若槻です。

今回は、前回の記事で実装したmaterial-tableのセレクトリストのスタイルをカスタマイズしようとしたらエラーとなってハマり、Reactでユーザ定義のコンポーネントを使う場合は名前の先頭を大文字としないといけないという教訓を得たので共有します。

実現したかったこと

material-table上でデータのフィルタリングを行うためのセレクトリストのスタイルをカスタマイズして、既定(未フィルター時)の横幅を大きくしたいです。 スクリーンショット_2020-08-16_21_31_02.png

やってみた

現在のセレクトリストのコンポーネントの定義は次のように何もスタイルは適用していません。

src/components/atoms/SelectList.tsx

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

const selectList = (props: {
  columnDef: any;
  onFilterChanged: (rowId: string, filterValue: string) => void;
  items: [string, string][];
}) => {
  const { columnDef, onFilterChanged, items } = props;

  const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
    onFilterChanged(columnDef.tableData.id, event.target.value as string);
  };

  return (
    <FormControl>
      <Select onChange={handleChange}>
        {items.map((item) => (
          <MenuItem value={item[0]}>{item[1]}</MenuItem>
        ))}
      </Select>
    </FormControl>
  );
};

export default selectList;

そこで、次のようにコードを変更しました。Material-UIでスタイルのユーザ定義を可能とするmakeStyles()を使って、minWidth: 80を定義し、<FormControl>コンポーネントに適用して最小幅を80としています。

ハイライト箇所が変更(行追加)部分です。

src/components/atoms/SelectList.tsx

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

const useStyles = makeStyles(() => ({
  formControl: {
    minWidth: 80,
  },
}));

const selectList = (props: {
  columnDef: any;
  onFilterChanged: (rowId: string, filterValue: string) => void;
  items: [string, string][];
}) => {
  const { columnDef, onFilterChanged, items } = props;
  const classes = useStyles();

  const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
    onFilterChanged(columnDef.tableData.id, event.target.value as string);
  };

  return (
    <FormControl className={classes.formControl}>
      <Select onChange={handleChange}>
        {items.map((item) => (
          <MenuItem value={item[0]}>{item[1]}</MenuItem>
        ))}
      </Select>
    </FormControl>
  );
};

export default selectList;

しかし、変更後にReactアプリを実行すると次のようなコンパイルエラーとなりました。 image.png

./src/components/atoms/SelectList.tsx
  Line 19:19:  React Hook "useStyles" is called in function "selectList" which is neither a React function component or a custom React Hook function  react-hooks/rules-of-hooks

Search for the keywords to learn more about each error.

調べてみたところReactドキュメントの次のページにある通り、Reactでユーザ定義のコンポーネントを使う場合は名前の先頭を大文字とする必要があるとのことです。

When an element type starts with a lowercase letter, it refers to a built-in component like <div> or <span> and results in a string 'div' or 'span' passed to React.createElement. Types that start with a capital letter like <Foo /> compile to React.createElement(Foo) and correspond to a component defined or imported in your JavaScript file.

コンポーネント名がselectListと小文字で始まっているため、useStyles<div><span>のような組み込みのコンポーネントの中で呼ばれたと判断されたようです。

そこでコンポーネント名をselectListからSelectListに変更したところ、

src/components/atoms/SelectList.tsx

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

const useStyles = makeStyles(() => ({
  formControl: {
    minWidth: 80,
  },
}));

const SelectList = (props: {
  columnDef: any;
  onFilterChanged: (rowId: string, filterValue: string) => void;
  items: [string, string][];
}) => {
  const { columnDef, onFilterChanged, items } = props;
  const classes = useStyles();

  const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
    onFilterChanged(columnDef.tableData.id, event.target.value as string);
  };

  return (
    <FormControl className={classes.formControl}>
      <Select onChange={handleChange}>
        {items.map((item) => (
          <MenuItem value={item[0]}>{item[1]}</MenuItem>
        ))}
      </Select>
    </FormControl>
  );
};

export default SelectList;

次のようにエラー無くReactアプリを実行でき、セレクトリストの既定の横幅も大きくすることができました。 スクリーンショット 2020-08-16 22.27.33.png

おわりに

material-tableのセレクトリストのスタイルをカスタマイズしようとしたらエラーとなってハマり、Reactでユーザ定義のコンポーネントを使う場合は名前の先頭を大文字としないといけないという教訓を得たという話でした。

Reactの基本のお作法ではあると思うのですが初心者にとってはハマりどころなのではないでしょうか。

参考

以上