Reactでpropsを扱う時に気をつける２つのケース

## はじめに
Reactはコンポーネント間の値をやり取りをpropsで行います。propsは親コンポーネントから子コンポーネントへと情報を渡す機能です。
```tsx
const Parent: FC = () => {
  const user = useUser();

  return <Child id={user.id} name={user.name} />;
};

const Child: FC<{ id: number, name: string }> = ({
  id,
  name,
}) => {
  return <User id={id} name={name} />;
};
```
上記のコンポーネントでは`Parent`コンポーネントから`Child`コンポーネントへ`id`と`name`を渡しています。この`id`や`name`がpropsと呼ばれています。
propsを渡されたコンポーネントはpropsが変化することで再レンダリングを起こします。

この記事ではそんなpropsを利用する中で気をつけるべき2つのケースを紹介します。

## ある値に計算するために扱う
propsに基づいて特定の値を計算したい場合、`useEffect`を用いて以下のように記述されることがあります。
```tsx
const Sample: FC<{ todos: Todo[] }> = ({ todos }) => {
  const [searchParams, setSearchParams] = useState<string>();
  const [displayTodos, setDisplayTodos] = useState<Todo[]>([]);

  useEffect(() => {
    setDisplayTodos(searchTodos(todos, searchParams));
  }, [todos, searchParams]);

  return ...;
};
```
この方法はマウント時や、`todos`や`searchParams`の変更がされた時に、レンダリングを行って画面へ反映した後に、エフェクトに記述した`displayTodos`の更新を行なって再度レンダリングと画面への反映を行うので非効率です。
そもそも、Reactでは`useEffect`をEscape Hatchをしており、React外のシステムとやりとりする時にのみ使用することを推奨しているのでした。
そのため、`useEffect`を使わずに書いてみます。`todos`と`searchParams`が変更されるたびに`displayTodos`を計算し直せば良いのでレンダー前の値を覚えておくようにして以下のように記述できます。
```tsx
const Sample: FC<{ todos: Todo[] }> = ({ todos }) => {
  const [searchParams, setSearchParams] = useState<string>();
  const [displayTodos, setDisplayTodos] = useState<Todo[]>([]);

  const [prevTodos, setPrevTodos] = useState<Todo[]>(todos);
  const [prevSearchParams, setPrevSearchParams] = useState<string>(searchParams);

  if (prevSearchParams !== searchParams) {
    setPrevSearchParams(searchParams);
    setDisplayTodos(searchTodos(todos, searchParams));
  }

  if (!isSame(todos, prevTodos) {
    setPrevTodos(todos);
    setDisplayTodos(searchTodos(todos, searchParams));
  }

  return ...;
};
```
コンポーネントの計算を行うタイミングで状態の更新を行うのはそのコンポーネントで定義されたものでなければいけないことに注意してください。そして、if文がない場合や、オブジェクトが単純な`Object.is`による比較で判定しようとした場合は無限ループに陥ることにも注意してください。
このコンポーネントはマウント時や、`todos`と`searchParams`が変更されるたびにレンダリングを行い、その計算中に状態の更新が行われるのでその子コンポーネントや画面の描画が起きる前に再レンダリングが引き起こされます。これは先ほどの`useEffect`を用いて記述した方式よりもパフォーマンスが少し良いです。
この書き方は、複雑に入り組んだコードを書く必要もありますし、パフォーマンスにも改善の余地があります(レンダリングが一回無駄)。以下のように書くことでシンプルでパフォーマンス上の不利も無くせます。
```tsx
const Sample: FC<{ todos: Todo[] }> = ({ todos }) => {
  const [searchParams, setSearchParams] = useState<string>();
  const displayTodos = searchTodos(todos, searchParams);

  return ...;
};
```
単純にpropsや状態を組み合わせて変数を作れば良いだけでした。この状態だとレンダリングのたびに`searchTodos`の計算が行われるので`useMemo`を用いて最適化を用いると良いです(`useMemo`を使う目安は計算にかかる処理が1ms以上かかる場合がおすすめです[参考](https://ja.react.dev/reference/react/useMemo#how-to-tell-if-a-calculation-is-expensive))。
propsや状態を用いた新しい値を扱うときは組み合わせて表現できるか確認してください。もし、propsによって表される状態ではなく、propsの変化によってリセットや変更したい状態の場合は少し複雑にはなりますが２個目に紹介したようなやり方で記述してください。`useEffect`を用いた変更はしないようにしましょう。

## 初期値として扱う
propsをコンポーネントで扱うために以下のように新たな状態として作り直すような記法が見受けられます。
```tsx
const Sample: FC<{ value: string }> = ({ value }) => {
  const [text, setText] = useState(value);

  const handleChange: ReactEventHandler<HTMLInputElement> = (e) => {
    setText(e.currentTarget.value);
  };

  return <input value={text} onChange={handleChange} />;
};
```
`text`を用いてコンポーネントの計算を行うように記述している場合は`value`の変化によって、コンポーネントの再レンダリングは引き起こされるものの、`text`の値は`value`の変化によって変化しないのでコンポーネントの計算結果は変わりません。
そして、`text`の変化も`value`を状態として扱う親コンポーネントには伝わらないことに注意してください。

`value`をコンポーネントの計算でそのまま利用させるには、propsをそのまま計算へ使うようにさせましょう。
```tsx
const Sample: FC<{ value: string }> = ({ value }) => {

  return <input value={value} onChange={??} />;
};
```
多くの場合先ほどのパターンに陥る理由はpropsを子コンポーネントで変化させたいからです。今回の変更はpropsを直接渡すようにしたことなので、それを更新するすべがなく`onChange`に渡すものがなくなりました。そんな時は親コンポーネントからpropsとして`value`を変化させる`onChange`も合わせて渡すようにします。
```tsx
const Sample: FC<{
  value: string,
  onChange: ReactEventHandler<HTMLInputElement>
}> = ({
  value,
  onChange,
}) => {

  return <input value={value} onChange={onChnage} />;
};
```
これで、親コンポーネントとつながりのある子コンポーネントして扱えるようになりました。
最初に見たpropsを新たな状態として扱う方法は、propsを初期値として扱う際は便利です。
```tsx
const Sample: FC<{ defaultValue: string }> = ({ defaultValue }) => {
  const [text, setText] = useState(defaultValue);

  const handleChange: ReactEventHandler<HTMLInputElement> = (e) => {
    setText(e.currentTarget.value);
  };

  return <input value={text} onChange={handleChange} />;
};
```
初期値として`props`を渡し、後の状態変化は子コンポーネントに託すようなケースでは上記のような書き方が適しています。その時は`defaultValue`のように`default`を名前につけるのが慣習となっています。
適材適所でそれぞれ活用していきたいです。