20201112のReactに関する記事は5件です。

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を使っていたので、×
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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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>
    </>
  )
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を使用することに成功!パチパチ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.jsx
class Blog extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isPublished: false,
      count: 0,
    };
  }

constructorの中で宣言したcountのstateをArticleに渡すと、Articleの中でcount数が分かる様になります

Blog.jsx
  render() {
    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.jsx
import React from "react";

// 関数名はファイル名と合わせる
const LikeButton = (props) => {
  return <button>いいね数:{props.count}</button>; // propsを引数に受け、いいね数をprops.countという形で受け取る
};

export default LikeButton;

Article.jsxLikeButtonimportします

Article.jsx
import LikeButton "./LikeButton";

次にLikeButtoncountを渡すのですが、Blog.jsxからはthis.state.countという内容をcountというpropsで受け取っているので

Blog.jsx
  render() {
    return (
      <>
        <Article title={"Reactの使い方"} isPublished={this.state.isPublished} toggle={() => this.togglePublished()} count={this.state.count} />
      </>
    );
  }

⑦ こちらもprops.countという形で渡してあげるとLikeButton.jsxも同じ様に参照できる様になります

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 count={props.count} />
    </div>
  );
};

ただ、現状はボタンを押しても何も変わりませんので、ボタンをクリックしたらいいね数を増やす処理を書きます

今の感じ.gif

countUp変数を作り、呼び出されたらstateを変更します(countの現在の値に+1する)

Blog.jsx
countUp = () => {
  this.setstate({
    count: this.state.count + 1,
  });
};

⑨ 今回はライフサイクルを使うので、イベントリスナーを設定します

Blog.jsx
componentDidMount() {
  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という形で受け取る
};

これでボタンをクリックすると、いいねが増える様になりました

カウントアップされていく.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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} />
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む