Reactでのformの挙動とfetchの備忘録

最近、Next.jsを勉強していたのですが、Reactのことを結構忘れていてこれはまずいとなったので、復習している中での備忘録になります。

今回は、`form`と`fetch`について。

APIは[jsonplaceholder](https://jsonplaceholder.typicode.com/)を使います。さまざまなでもデータを用意してくれていて便利です。`https://jsonplaceholder.typicode.com/`というURLに`/posts`や`/todos`、`/users`などをつけることで違ったデータを参照できます。

そこで今回は、formのinputに文字を入力することで、postsやtodosなどを指定してデータをとってくるようにしたいと思います。

完成形は以下の通りです。この場合はfetchするURLが`https://jsonplaceholder.typicode.com/todos`となっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/509690/17eba533-8df7-9781-33fb-0046caba2287.png)


言葉で説明するよりもコードを見ていただいた方がわかりやすいと思うので、コードを載せます。

```Home.tsx
import React, { useState, useRef } from "react";

const Home: React.FC = () => {
  const [data, setData] = useState([]);
  const [text, setText] = useState("");
  const titleRef: any = useRef();

  const handlesubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    setText(titleRef.current.value);
    console.log(text);
    fetch(`https://jsonplaceholder.typicode.com/${text}`)
      .then((res) => {
        const data = res.json();
        return data;
      })
      .then((data) => setData(data));
  };

  return (
    <div>
      <form onSubmit={handlesubmit}>
        <input type="text" ref={titleRef} />
        <input type="submit" />
      </form>
      <div>
        <h2>ここにリスト表示</h2>
        <ul>
          {data.map((d: any) => (
            <li key={d.id} style={{ listStyle: "none" }}>
              {d.title}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default Home;
```

inputに入力される値のやりとりは`useRef`を用いています。submitボタンを押すと、`onSubmit={handlesubmit}`が動いて、`fetch`などを実行します。繰り返しになりますが、`handlesubmit`は以下の通りです。

```Home.tsx
  const handlesubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    setText(titleRef.current.value);
    fetch(`https://jsonplaceholder.typicode.com/${text}`)
      .then((res) => {
        const data = res.json();
        return data;
      })
      .then((data) => setData(data));
  };
```

`titleRef.current.value`とすることで、inputに入力された値を参照し、`setText(titleRef.current.value)`とすることで、その値を`text`というstateに持たせることができます。

そして`fetch`をします。URLのなかで、先ほど`text`に持たせた値を展開することで、任意のデータを取得できるようにします。

[fetchはPromiseを返す非同期処理](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)なので、`then()`を使って処理していきます。`res.json()`はfetchして返されたデータをJSON形式にして解釈します。そのデータを格納した`data`をそのままstateに持たせたいところですが、`res.json()`も非同期処理であるため、`先にPromiseが返され`、その後にデータが返されます。

なので、

```Home.tsx
    fetch(`https://jsonplaceholder.typicode.com/${text}`)
      .then((res) => {
        const data = res.json();
        setData(data)
      })
```

このように記述すると、dataに何も入っていない状態になってしまうので、エラーになります。TypeScriptの場合はエディタで波線が引かれるはずです。ですので、`setData`の部分も`then`で繋ぐ必要があります。

さて、これで無事に取得したデータを`data`というstateに持たせることができました。dataは配列なので、`map`で回す必要があります。ですので、以下のようになりました。

```Home.tsx
    <div>
      <form onSubmit={handlesubmit}>
        <input type="text" ref={titleRef} />
        <input type="submit" />
      </form>
      <div>
        <h2>ここにリスト表示</h2>
        <ul>
          {data.map((d: any) => (
            <li key={d.id} style={{ listStyle: "none" }}>
              {d.title}
            </li>
          ))}
        </ul>
      </div>
    </div>
```

本当はmapメソッドの引数(今回は`d`)の型はちゃんとつけたほうがいいのですが、今回は割愛します。

これで、formで入力したjsonplaceholderのURLからデータを取得し、表示することができました。