- 投稿日:2020-11-12T22:31:21+09:00
ReactのformをBootstrapからMaterial UIに移行する際の注意点
Reactを勉強する際に、YouTubeやUdemyなど様々なところで超有益な動画がたくさんあります。特に英語での解説がされている動画をみていると、短い時間で綺麗なデザインで、かつ機能もある程度しっかり実装されていることが多いです。
このような短時間で綺麗なデザインのwebアプリを作っている動画では、React Bootstrapが使われていることが多いです。
ただ、「最近はMaterialデザイン流行ってそうだし、MaterialUIでも導入してみるか」と言うことで、後から作り替えるなんて方もいるかもしれません。
この記事ではそんな
「BootstrapからMaterial UIに移行したい」と考えている方で、その中でも特に移行したい機能の一部にFormがあると言う方向けになっています。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を使っていたので、
×と言うエラーが出ましたが、普通のJavaScriptなら
Unhandled Rejection (FirebaseError): Function DocumentReference.set() called with invalid data. Unsupported field value: undefinedUncaught 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.jsxexport 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
- 投稿日:2020-11-12T17:39:09+09:00
【React】チェックボックスにチェックされたときだけ送信ボタンを有効化(関数コンポーネント)
プライバシーポリシーに同意するにチェックがあったときだけ、問い合わせを送信できるようにした時に。
required属性つけるだけでも実装できるけど、HTMLのフォームエラーのデザインが気に入らない時などに。概要
isCheckedというstate(boolean)を作成- 送信ボタンのdisabled属性に
isCheckedをセットisCheckedの値を変化させる関数(toggleCheckbox)を作成toggleCheckboxをチェックボックスの状態が変化した時に発火コード
import React, { useState } from "react" export default () => { const [isChecked, setIsChecked] = useState(false) const toggleCheckbox = () => { setIsChecked(!isChecked) } return ( <> <h2>お問い合わせ</h2> <form method="post" action="#"> <label htmlFor="email">メールアドレス</label> <input type="text" name="email" id="contact_email" /> <label htmlFor="content">お問い合わせ内容</label> <textarea name="content" id="contact_content" cols="30" rows="10" ></textarea> <p>利用規約に同意します。</p> <input type="checkbox" name="agree" id="agreeCheck" onChange={() => toggleCheckbox()} /> <label htmlFor="agreeCheck">同意する</label> <div> <button type="submit" disabled={!isChecked}>送信</button> </div> </form> </> ) }
- 投稿日:2020-11-12T14:00:19+09:00
Material UIの@material-ui/pickersで少しハマったので自分用に解決策を残しておく
@material-ui/pickersを使う手順
1インストールするnpm i @material-ui/pickers or yarn add @material-ui/pickers
以下Documentより
Important: For material-ui-pickers v3 use v1.x version of @date-io adapters.
⇨material-ui-pickers v3の場合は、v1.xバージョンの@ date-ioアダプターを使用してください。
とのことです。
1インストールするnpm i @date-io/date-fns@1.x date-fns // or npm i @date-io/moment@1.x moment // or npm i -s @date-io/luxon@1.x luxon // or npm i -s @date-io/dayjs@1.x dayjs
自分はdate-fnsをインストールして無事material-uiを使用することに成功!パチパチ
- 投稿日:2020-11-12T13:07:14+09:00
Reactを学ぶVII 〜ライフサイクル知識編〜
■ はじめに
タイトルについて記事にしました。
この記事で得る内容は以下の通りです。・ Reactの基礎知識が増える
・ Reactのライフサイクルについて■ ライフサイクルとは
・ コンポーネントの「時間の流れ」
→ ユーザーの操作によって作られ、変更されたり、破棄されたりする循環
・ それぞれの段階で必要な処理を記述
→ Reactのライフサイクルメソッドというものを使う■ 3種類のライフサイクルについて
・ Mounting
→ コンポーネントが配置される(生まれる)期間
(URLにアクセスしてページにReactで作ったコンポーネントが配置されるなど)・ Updating
→ コンポーネントが変更される(成長する)期間
(ユーザーがボタンを押した場合、ある箇所の色を変えるなど)・ Unmounting
→ コンポーネントが破棄される(死ぬ)期間
(現在のページから別のページにアクセスするなど)Reactではこの3つの期間で使えるメソッドが用意されている
■ なぜライフサイクルを使うのか
→ Reactコンポーネントの外に影響を与える関数を記述する時にライフサイクルを使う
(Reactで作ったVirtual DOMを変更する時やDB通信(API)、ログ出力、setstate( ) etc...)・ 副作用
→ よくReactで、副作用のある処理を記述しようと言われますが、それは適切な場所(ライフサイクル)に配置すべき処理のことを指します■ 主要メソッド(Mount)
・ constructor()
→ 初期化(stateなど)これからコンポーネントで使おうとしている変数など最初に定義する場合に使う
・ render()
→ Virtual DOMを描画(JSXをリターン)
・ compornentDidMount()
→ render()後に1度だけ呼ばれ、リスナーの設定やAPI通信に使われる
■ 主要メソッド(Update)
・ render()
→ VDOMを再描画
・ compornentDidMount()
→ 再render後に呼ばれるスクロールイベントや条件付きイベント
■ 主要メソッド(Unmount)
・ componentWillUnmount
→ コンポーネントが破棄される直前に使われる(リソースの開放やリスナーの解除など)
■ 例
例として、いいねボタンを作ってみます。前回書いたのをそのまま使います
① まず、
this.stateの中にcount: 0,と書いてBlog.jsxclass Blog extends React.Component { constructor(props) { super(props); this.state = { isPublished: false, count: 0, }; }②
constructorの中で宣言したcountのstateをArticleに渡すと、Articleの中でcount数が分かる様になりますBlog.jsxrender() { return ( <> <Article title={"Reactの使い方"} isPublished={this.state.isPublished} toggle={() => this.togglePublished()} count={this.state.count} /> </> ); }③ 次に、
Article.jsxに<LikeButton />というコンポーネントを記述してArticle.jsx// 中略 const Article = (props) => { return ( <div> <h2>{props.title}</h2> <label htmlFor="check">公開状態:</label> <input type="checkbox" checked={props.isPublished} id="check" onClick={() => props.toggle()} /> <LikeButton /> </div> ); };④
LikeButton.jsxというファイルを新規作成して、次の様に記述しますLikeButton.jsximport React from "react"; // 関数名はファイル名と合わせる const LikeButton = (props) => { return <button>いいね数:{props.count}</button>; // propsを引数に受け、いいね数をprops.countという形で受け取る }; export default LikeButton;⑤
Article.jsxでLikeButtonをimportしますArticle.jsximport LikeButton "./LikeButton";次に
LikeButtonにcountを渡すのですが、Blog.jsxからはthis.state.countという内容をcountというpropsで受け取っているのでBlog.jsxrender() { return ( <> <Article title={"Reactの使い方"} isPublished={this.state.isPublished} toggle={() => this.togglePublished()} count={this.state.count} /> </> ); }⑦ こちらも
props.countという形で渡してあげるとLikeButton.jsxも同じ様に参照できる様になりますArticle.jsxconst Article = (props) => { return ( <div> <h2>{props.title}</h2> <label htmlFor="check">公開状態:</label> <input type="checkbox" checked={props.isPublished} id="check" onClick={() => props.toggle()} /> <LikeButton count={props.count} /> </div> ); };ただ、現状はボタンを押しても何も変わりませんので、ボタンをクリックしたらいいね数を増やす処理を書きます
⑧
countUp変数を作り、呼び出されたらstateを変更します(countの現在の値に+1する)Blog.jsxcountUp = () => { this.setstate({ count: this.state.count + 1, }); };⑨ 今回はライフサイクルを使うので、イベントリスナーを設定します
Blog.jsxcomponentDidMount() { document.getElementById("counter").addEventListener("click", this.countUp); } // this.countUp()の様な書き方だと無限ループしてしまうので、関数型() =>及び、関数だけを渡す⑩ イベントリスナーの設定先に
idを設定しますArticle.jsx// 関数名はファイル名と合わせる const LikeButton = (props) => { return <button id={"counter"}>いいね数:{props.count}</button>; // propsを引数に受け、いいね数をprops.countという形で受け取る };これでボタンをクリックすると、いいねが増える様になりました
- 投稿日:2020-11-12T11:12:09+09:00
React 画像 遅延読み込み
type Props = { src: string; } const Image: FC<Props> = (props) => { const [isIntersecting, setIsIntersecting] = useState(false); const imgElRef = useRef<HTMLImageElement>(null); useEffect(() => { const lazyImageObserver = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { setIsIntersecting(true); } }); lazyImageObserver.observe(imgElRef!.current!); return (): void => { lazyImageObserver.unobserve(imgElRef!.current!); }; }, []); return <img src={props.src} /> }

