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はしっかり設定しよう
と言うところなのですが、そこについて深掘ります。
以下のコードをご覧ください
//一部省略してます
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を加えたコードが以下になります。
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