ReactのformをBootstrapからMaterial UIに移行する際の注意点

Reactを勉強する際に、YouTubeやUdemyなど様々なところで超有益な動画がたくさんあります。特に英語での解説がされている動画をみていると、短い時間で綺麗なデザインで、かつ機能もある程度しっかり実装されていることが多いです。

このような短時間で綺麗なデザインのwebアプリを作っている動画では、[React Bootstrap](https://react-bootstrap.github.io/)が使われていることが多いです。

ただ、「最近はMaterialデザイン流行ってそうだし、MaterialUIでも導入してみるか」と言うことで、後から作り替えるなんて方もいるかもしれません。

この記事ではそんな`「BootstrapからMaterial UIに移行したい」`と考えている方で、その中でも特に`移行したい機能の一部にFormがある`と言う方向けになっています。

### (2020/12/7 追記)
この記事で解説している内容以外にもTextFieldの値を送信する方法はあります。また、この記事で使っているコードのjsxの部分で`ref={}`を使っている部分があるのですが、Material UIではrefは`inputRef`としないと正しく動作しないことがあります。その様なことを含め、
[「React+Material UIのformやTextfieldがうまく動作しない」そんな場合のチェックポイント](https://qiita.com/KOBA-RYOTA/items/9a088b87b6626001f84b)
という記事を作成しましたので、よかったらご覧ください🙆‍♂️

## TextFieldでハマった😨
この記事で一番伝えたいのは、`BootstrapからMaterial UIに移す時に、valueはしっかり設定しよう`と言うところなのですが、そこについて深掘ります。

以下のコードをご覧ください

```PostForm.jsx
//一部省略してます
export default function PostForm() {
    const classes = useStyles();
    const [error, setError] = useState("")
    const [loading, setLoading] = useState(false)
    const history = useHistory()
    const titleRef = useRef()
    const contentRef = useRef()
    const { createPost, currentUser } = useAuth()

    const handleSubmit = async(e) => {
      e.preventDefault();

      setError("")
      setLoading(true)
      const authorName = currentUser.username
      createPost(titleRef.current.valur, contentRef.current.value, authorName)
      history.push("/")
      setError("投稿に失敗しました")
      setLoading(false)
    }
    
    return (
        <>
          {error && <Alert variant="danger">{error}</Alert>}
              <h2 className={classes.header}>新規投稿を作成</h2>
              <form className={classes.root} noValidate autoComplete="off" onSubmit={handleSubmit}>
                  <div className={classes.postFormBox}>
                    <TextField 
                      type="text" 
                      label="タイトル" 
                      ref={titleRef} 
                      className={classes.postFormTextField} 
                      multiline={true}
                      required
                    />
                    <br/>
                    <TextField 
                      type="text" 
                      label="内容" 
                      ref={contentRef} 
                      className={classes.postFormTextField} 
                      multiline={true}
                      rows={4} 
                      required 
                    />
                    <br/>
                    <Button 
                      variant="contained" 
                      type="submit" 
                      color="primary" 
                      disabled={loading}
                    >
                      投稿
                    </Button>
                  </div>
              </form>      
        </>
    )
}
```
`タイトル`と`投稿内容`を入力するためのTextFieldにsubmit用のボタン、さらにはボタンが押された時に関数handleSubmitが呼ばれ、handleSubmit内の処置が走ると言うシンプルなformのコンポーネントです。

これをみると一見、動きそうに見えますが、handleSubmitのcreatePostの処理でエラーがおきます。

どのようなエラーかと言うと、定義されていない値を参照してしまうエラーで、僕の場合はfirestoreを使っていたので、`×
Unhandled Rejection (FirebaseError): Function DocumentReference.set() called with invalid data. Unsupported field value: undefined`と言うエラーが出ましたが、普通のJavaScriptなら`Uncaught TypeError: Cannot read property ‘プロパティ名’ of undefined`と言うエラーになります。

これは`undifined`の値を参照しようとしている時に起こります。

そうです、上記のコードではTextField、つまりinputのvalueがundifinedとして返されてしまいます。

なので、inputでvalueをせってしてあげる必要があります。

### 解決策
今回はReactを利用しているので、useStateでそれぞれのvalueのstateを作る必要があります。
上記の例で言うなら、タイトルには`title`、内容には`content`と言う名前でstateを持たせます。

さらにそれらのstateの変更の度にコンポーネントを再レンダリングするためのuseCallbackを使ったinputContentとinputTitleを加えたコードが以下になります。

```PostForm.jsx

export default function PostForm() {
    const classes = useStyles();
    const [error, setError] = useState("")
    const [title, setTitle] = useState("")
    const [content, setContent] = useState("")
    const [loading, setLoading] = useState(false)
    const history = useHistory()
    const titleRef = useRef()
    const contentRef = useRef()
    const { createPost, currentUser } = useAuth()

    const inputTitle = useCallback((event) => {
      setTitle(event.target.value)
    }, [setTitle]);
    
    const inputContent = useCallback((event) => {
      setContent(event.target.value)
    }, [setContent]);

    const handleSubmit = async(e) => {
      e.preventDefault();

      setError("")
      setLoading(true)
      setTitle("")
      setContent("")
      const authorName = currentUser.username
      createPost(title, content, authorName)
      history.push("/")
      setError("投稿に失敗しました")
      setLoading(false)
    }
    
    return (
        <>
          {error && <Alert variant="danger">{error}</Alert>}
          <Card className={classes.card}>
            <CardContent>
              <h2 className={classes.header}>新規投稿を作成</h2>
              <form className={classes.root} noValidate autoComplete="off" onSubmit={handleSubmit}>
                  <div className={classes.postFormBox}>
                    <TextField 
                      type="text" 
                      label="タイトル" 
                      ref={titleRef} 
                      className={classes.postFormTextField} 
                      multiline={true}
                      required
                      //大切なのはここ!!valueをちゃんと設定する！！
                      value={title}
                      onChange={inputTitle}
                    />
                    <br/>
                    <TextField 
                      type="text" 
                      label="内容" 
                      ref={contentRef} 
                      className={classes.postFormTextField} 
                      multiline={true}
                      rows={4} 
                      required 
                      //大切なのはここ!!valueをちゃんと設定する！！
                      value={content}
                      onChange={inputContent}
                    />
                    <br/>
                    <Button 
                      variant="contained" 
                      type="submit" 
                      color="primary" 
                      style={{ width: "100%", marginTop: "15px" }}
                      disabled={loading}
                    >
                      投稿
                    </Button>
                  </div>
              </form>  
            </CardContent>
          </Card> 
          <Link to="/" className={classes.cancel}>
            キャンセル
          </Link>          
        </>
    )
}
```

## まとめ

**ちゃんとvalueを設定しよう**

*ついでですが、buttonのtypeは忘れずに設定しましょう。僕はsubmitを忘れて、エラーも出ずほんと数時間無駄にしました...w*