Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

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

KOBA-RYOTA
フロントエンドエンジニアでの就職を目指している23歳です。 趣味でRubyやPHP、大学の卒業研究でPythonを使いましたが、特に好きなのはJavaScriptです。 現在はReactにどハマりしています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away