LoginSignup
0
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-11-12

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

このような短時間で綺麗なデザインのwebアプリを作っている動画では、React Bootstrapが使われていることが多いです。

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

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

(2020/12/7 追記)

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

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1