React ComponentのPropsには何を渡すかよく考えて欲しい

## React ComponentのPropsには好きなものを渡せる

使いやすいReact Componentを作るために、何をPropsとして渡せば良いか考えたことはありますか？

Propsに何でも渡せるのがReact Componentの良さですが、自由度が高いために何も考えないと使いにくいコンポーネントになってしまいます。


## 使いにくいコンポーネントの例

TODOリストのアプリの新しいTODOを追加する部分を思い浮かべて下さい。イメージとして[TODO MVC](https://todomvc.com)を拝借しますが、この部分です。

![todomvc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279669/479e21f9-2cce-41cb-94cd-1572e8c1d071.png)

TODO MVCのコードでは無いことを断っておきますが、コードはこんな感じでしょうか。APIとStoreは適当にRESTやReduxを思い浮かべてくれれば良いです。

```tsx
import { useState } from 'react';
import { postTodoInAPI } from '@api';

const NewTodoForm = ({ addTodo, postTodoInAPI }) => {
  const [title, setTitle] = useState('');

  const handleChange = (e) => setTitle(e.target.value);

  const handleSubmit = (e) => {
    e.preventDefault();
    const newTodo = { title };
    postTodoInAPI(newTodo);
    addTodo(newTodo);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name='title' value={title} onChange={handleChange} />
    </form>
  );
};

const TodoList = () => {
  const { todos, addTodo } = useStore();

  return (
    <div>
      <NewTodoForm addTodo={addTodo} postTodoInAPI={postTodoInAPI} />
      <ul>
        {todos.map(({ title }, i) => (
          <li key={i}>{title}</li>
        ))}
      </ul>
    </div>
  );
};
```

このコードにはPropsの渡し方のせいで使いにくいコンポーネントになっている部分があります。

`NewTodoForm` に `addTodo` と `postTodoInAPI` を渡しているせいで、TODOを追加するときのロジックを `NewTodoForm` の中で組み立てています。このせいで `NewTodoForm` がAPIとStoreに依存するようになってしまいます。

例えば、インフラのコスト削減のためにDBを廃止して、代わりに `localStorage` を使うようになったとします。APIが不要になるので `postTodoInAPI` を廃止して、代わりに `addTodoInLocalStorage` を使うようになります。

```diff_tsx
  import { useState } from 'react';
- import { postTodoInAPI } from '@api';
+ import { addTodoInLocalStorage } from '@localstorage';

- const NewTodoForm = ({ addTodo, postTodoInAPI }) => {
+ const NewTodoForm = ({ addTodo, addTodoInLocalStorage }) => {
    const [title, setTitle] = useState('');

    const handleChange = (e) => setTitle(e.target.value);

    const handleSubmit = (e) => {
      e.preventDefault();
      const newTodo = { title };
-     postTodoInAPI(newTodo);
+     addTodoInLocalStorage(newTodo);
      addTodo(newTodo);
    };

    return (
      <form onSubmit={handleSubmit}>
        <input name='title' value={title} onChange={handleChange} />
      </form>
    );
  };

  const TodoList = () => {
    const { todos, addTodo } = useStore();

    return (
      <div>
-       <NewTodoForm addTodo={addTodo} postTodoInAPI={postTodoInAPI} />
+       <NewTodoForm addTodo={addTodo} addTodoInLocalStorage={addTodoInLocalStorage} />
        <ul>
          {todos.map(({ title }, i) => (
            <li key={i}>{title}</li>
          ))}
        </ul>
      </div>
    );
  };
```

このときに `NewTodoForm` と `TodoList` の2つのコンポーネントで改修が発生していまいます。`NewTodoForm` を別のコンポーネントでも使っていた場合、そこに影響しないように注意を払わなければならず、最悪の場合 `NewTodoForm` を共通で使えなくなってしまうかもしれません。

ここで言えるのは `NewTodoForm` は再利用しやすいコンポーネントではなかったということです。それは責務を考えずに `NewTodoForm` にPropsを渡していたからです。


## 使いやすいコンポーネントの例

`NewTodoForm` の責務は「新しいTODOを追加すること」であり、新しいTODOをどこ(DB or `localStorage`)に追加するかは `NewTodoForm` の関与するところでは無いのです。`NewTodoForm` は新しいTODOを追加するためのfunctionを知っていれば良く、新しいTODOをAPIや `localStorate` や Store に追加するfunctionは知る必要がないのです。

それはどういうコードなのかというと、こうなります。

```diff_tsx
  import { useState } from 'react';
  import { postTodoInAPI } from '@api';

- const NewTodoForm = ({ addTodo, postTodoInAPI }) => {
+ const NewTodoForm = ({ onAdd }) => {
    const [title, setTitle] = useState('');

    const handleChange = (e) => setTitle(e.target.value);

    const handleSubmit = (e) => {
      e.preventDefault();
      const newTodo = { title };
-     addTodo(newTodo);
-     postTodoInAPI(newTodo);
+     onAdd(newTodo);
    };

    return (
      <form onSubmit={handleSubmit}>
        <input name='title' value={title} onChange={handleChange} />
      </form>
    );
  };

  const TodoList = () => {
    const { todos, addTodo } = useStore();

+   const handleAdd = (newTodo) => {
+     postTodoInAPI(newTodo);
+     addTodo(newTodo);
+   }
+
    return (
      <div>
-       <NewTodoForm addTodo={addTodo} postTodoInAPI={postTodoInAPI} />
+       <NewTodoForm onAdd={handleAdd} />
        <ul>
          {todos.map(({ title }, i) => (
            <li key={i}>{title}</li>
          ))}
        </ul>
      </div>
    );
  };
```

これならAPIから `localStorage` に乗り換えても `TodoList` の `handleAdd` を変えるだけで、 `NewTodoForm` には影響しません。

```diff_tsx
  import { useState } from 'react';
- import { postTodoInAPI } from '@api';
+ import { addTodoInLocalStorage } from '@localstorage';

  const NewTodoForm = ({ onAdd }) => {
    const [title, setTitle] = useState('');

    const handleChange = (e) => setTitle(e.target.value);

    const handleSubmit = (e) => {
      e.preventDefault();
      const newTodo = { title };
      onAdd(newTodo);
    };

    return (
      <form onSubmit={handleSubmit}>
        <input name='title' value={title} onChange={handleChange} />
      </form>
    );
  };

  const TodoList = () => {
    const { todos, addTodo } = useStore();

    const handleAdd = (newTodo) => {
-     postTodoInAPI(newTodo);
+     addTodoInLocalStorage(newTodo);
      addTodo(newTodo);
    }
 
    return (
      <div>
        <NewTodoForm onAdd={handleAdd} />
        <ul>
          {todos.map(({ title }, i) => (
            <li key={i}>{title}</li>
          ))}
        </ul>
      </div>
    );
  };
```


## 終わりに

今回はシンプルの例だったので重要性が伝わりにくかったかもしれないですが、このような小さな積み重ねが日々の開発効率の改善に繋がります。せっかくReactをつかっているのですから、コンポーネントは再利用しやすいようにしておくに越したことはないのではないでしょうか。