10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Next.jsでInputフィールドにバリデーションをつけよう(初級編)

Posted at

前提知識

  • Reactの基礎(useState, useEffect)
  • Next.jsの基礎
  • TypeScriptの基礎

対象

  • JavaScriptは理解できている
  • React初学者

使用技術

  • Next.js
  • TypeScript
  • Sass

この記事で行うこと

今回はReactを用いてinputフィールドにフロント側からバリデーションをかけていきます。
今回は初級編と応用編に分けておこなっていきます!

初級編はまずはtodoアプリを用いてinputフィールドを作成し、
応用編ではinputフィールドをcomponentに落とし込んで再利用可能にしていきます。

さっそくやってみる

まずゴールとしては簡単なtodoを作成するだけにするので

  1. inputフィールドにタスクを追加する
  2. 追加ボタンを押す
  3. todoエリアに追加される

こちらの基本的な工程から行っていきます。

※今回はNext.jsを使用しています。

pages/input/index.tsx
import styled from "./input.module.scss"

export default function Input() {
  return (
    <>
      <h1 className={styled.heading}>インプットフィールドの練習</h1>
    </>
  );
};

pages/input/input.module.scss
.heading {
  font-size: 30px;
  font-weight: bold;
}

まず簡単に二つのファイルを作成しましょう。

ではこちらにTodoの追加フローを足していきます。
コードを書いた後に説明していきます。

pages/input/index.tsx
import styled from "./input.module.scss"
import React, {useState} from "react";

export default function Input() {

  const [ todoText, setTodoText ] = useState("");
  const [ todos, setTodos ] = useState([]);

  const onChangeTodoText = (event: React.ChangeEvent<HTMLInputElement>): void => setTodoText(event.target.value);

  const onClickAddTodoText = (): void => {
    const newArrayTodos = [...todos, todoText];
    setTodos(newArrayTodos);
    setTodoText("");
  }

  return (
    <>
      <h1 className={styled.heading}>インプットフィールドの練習</h1>
      <div className={styled.inputArea}>
        <input
          className={styled.todoInputField}
          placeholder={"todoを入力してください"}
          type={"text"}
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button
          className={styled.addTodoButton}
          onClick={onClickAddTodoText}
        >todoを追加</button>
      </div>

      <div className={styled.todoArea}>
        <h2 className={styled.heading2}>Todoリスト</h2>
        {todos.length === 0 ? (
          <p className={styled.md10}>todoはまだ登録されていません</p>
        ) : (
          <ul className={styled.md10}>
            {todos.map((todo) => (
              <li key={todo}>{ todo }</li>
            ))}
          </ul>
        ) }
      </div>
    </>
  );
};
pages/input/input.module.scss
.heading {
  font-size: 30px;
  font-weight: bold;
}

.heading2 {
  font-size: 24px;
  font-weight: bold;

  margin-top: 20px;
}

.md10 {
  margin-top: 10px;
}


.inputArea {

  margin-top: 20px;

  .todoInputField {
    margin-right: 5px;
  }
}

スクリーンショット 2021-07-28 0.53.52.png
こちらが表示されたかと思います。
では簡単に説明していきます。

 <input
   className={styled.todoInputField}
   placeholder={"todoを入力してください"}
   type={"text"}
   value={todoText}
   onChange={onChangeTodoText}
 />

まずはこちらからみていきましょう。
inputのtypeはtextにし、valueでtodoTextを受け取っています。
todoTextは

const [ todoText, setTodoText ] = useState("");

こちらでstateとして定義しています。
初期値は空文字列なのでinputの中身はplaceholderが表示されている状態です。

このままではvalueが空文字で設定されているので文字を入力することができません。
ここでonChangeイベントを使っています。
onChangeはinputフィールドに変更があった時に反応するイベントです。
ここでonChangeTodoTextの関数が発火します!

const onChangeTodoText = (event: React.ChangeEvent<HTMLInputElement>): void => setTodoText(event.target.value);

こちらですね。
inputフィールドはeventを引数に受け取ります。
これはもうお決まり事項なので暗記しましょう。
eventの型もお決まりといった形なのですが、これは覚えるというよりかは都度eventの型はこんな感じかというのを把握して都度ググるといった感じで大丈夫だと思います。

setTodoTextでtodoTextの初期値を更新しています。
event.target.valueでinputフィールドに入力された値を受け取っています!
こちらもお決まりなので暗記です!!

これでinputフィールドが入力できるようになっています。

        <button
          className={styled.addTodoButton}
          onClick={onClickAddTodoText}
        >todoを追加</button>

todoの追加ボタンでClickイベントを発火させています。

const onClickAddTodoText = (): void => {
  const newArrayTodos = [...todos, todoText];
  setTodos(newArrayTodos);
  setTodoText("");
}

newArrayTodosで新しい配列を生成しております。

const newArrayTodos = [...todos, todoText];

こちらはスプレット構文です。
[...もとの配列, 追加する実態] = 新しい配列
といったイメージです。

昔は.pushなどで配列にボコボコ追加していくスタイルが主流でしたが、pushなどは破壊的メソッドで少し危険な部分があるため、基本的にはスプレット構文を使うことをおすすめいたします。

  setTodos(newArrayTodos);
  setTodoText("");

新しく生成した配列をsetTodosで空の初期値の配列を更新しています。
const [ todos, setTodos ] = useState([]);
初期値はstateで定義してあります。

さらに最後にinputフィールドから追加された際はinputフィールドから文字は消えてほしいためsetTodoTextを空文字に更新しています。

      <div className={styled.todoArea}>
        <h2 className={styled.heading2}>Todoリスト</h2>
        {todos.length === 0 ? (
          <p className={styled.md10}>todoはまだ登録されていません</p>
        ) : (
          <ul className={styled.md10}>
            {todos.map((todo) => (
              <li key={todo}>{ todo }</li>
            ))}
          </ul>
        ) }
      </div>

追加の処理がかけたのであとはtodoを表示していきます。

三項演算子を活用し
todo.length === 0 ?のとき、つまり配列の数が0のとき
"todoはまだ登録されていません"を表示しています。

逆にtodoが登録された場合は

  {todos.map((todo) => (
    <li key={todo}>{ todo }</li>
  ))}

mapで配列を回しています。

こちらでtodo作成するフローは終了です。

バリデーションをかけていく

今回は
- Todoは0文字では追加できない
- Todoは10文字以内

といったバリデーションを練習としてかけていきます。

ではファイルを更新していきましょう。
こちら一応今回の完成系のコードになります。
では一つずつ確認していきましょう。

pages/input/index.tsx
import styled from "./input.module.scss"
import React, {useEffect, useState} from "react";

export default function Input() {

  const [ todoText, setTodoText ] = useState<string>("");
  const [ todos, setTodos ] = useState<Array<string>>([]);
  const [ isError, setIsError ] = useState<boolean>(false);
  const [ errorMessage, setErrorMessage ] = useState<string>("");

  const todoLengthZero = (): boolean => {
    if (todoText.length === 0) return true;
  }

  const onChangeTodoText = (event: React.ChangeEvent<HTMLInputElement>): void => {

    if (todoText.length >= 10) {
      setIsError(true)
      setErrorMessage("Todoは10文字いないで入力してください")
    } else {
      setIsError(false)
    }

    setTodoText(event.target.value);
  }

  const onClickAddTodoText = (): void => {

    const newArrayTodos = [...todos, todoText];
    setTodos(newArrayTodos);
    setTodoText("");
  }

  useEffect((): void => {
    todoLengthZero()
  }, [])

  return (
    <>
      <h1 className={styled.heading}>インプットフィールドの練習</h1>
      <div className={styled.inputArea}>
        {isError && <p className={styled.errorMessage}>{ errorMessage }</p>}
        <input
          className={styled.todoInputField}
          placeholder={"todoを入力してください"}
          type={"text"}
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button
          className={styled.addTodoButton}
          onClick={onClickAddTodoText}
          disabled={isError || todoLengthZero()}
        >todoを追加</button>
      </div>

      <div className={styled.todoArea}>
        <h2 className={styled.heading2}>Todoリスト</h2>
        {todos.length === 0 ? (
          <p className={styled.md10}>todoはまだ登録されていません</p>
        ) : (
          <ul className={styled.md10}>
            {todos.map((todo) => (
              <li key={todo}>{ todo }</li>
            ))}
          </ul>
        ) }
      </div>
    </>
  );
};

まずエラーメッセージの定義からです。

{isError && <p className={styled.errorMessage}>{ errorMessage }</p>}

こちらでエラーがある場合のみエラーメッセージを表示するように設定しています。
エラーのフラグはisErrorで定義していますが、

const [ isError, setIsError ] = useState<boolean>(false);

こちらはstateで初期値をfalseで定義しています。
なのでデフォルトはエラ〜メッセージが表示されません。

その下のinputフィールドをみていきましょう。

        <input
          className={styled.todoInputField}
          placeholder={"todoを入力してください"}
          type={"text"}
          value={todoText}
          onChange={onChangeTodoText}
        />

こちらはonChange(フィールドに入力された際に発火)イベントでonChangeTodoTextの関数を呼び出しています。

  const onChangeTodoText = (event: React.ChangeEvent<HTMLInputElement>): void => {

    if (todoText.length >= 10) {
      setIsError(true)
      setErrorMessage("Todoは10文字いないで入力してください")
    } else {
      setIsError(false)
    }

    setTodoText(event.target.value);
  }

こちらです。
内容としてはtodo.length >= 10(todoの文字数が10以上であれば)setIsErrorをtrueにし
setErrorMessageを入力します。

const [ isError, setIsError ] = useState<boolean>(false);
const [ errorMessage, setErrorMessage ] = useState<string>("");

こちらで初期値が定義されていたので変わります。
ここでisErrorがtrueになるため

{isError && <p className={styled.errorMessage}>{ errorMessage }</p>}

先ほどのerrorMessageが表示されるようになります。

こちらで10文字以上のエラーメッセージの表示は完了です。

次は0文字のバリデーションは初めからエラーメッセージが表示されるのは少しうざいのでdisabledを処理していきます。

        <button
          className={styled.addTodoButton}
          onClick={onClickAddTodoText}
          disabled={isError || todoLengthZero()}
        >todoを追加</button>

disabled={isError || todoLengthZero()}まずこちらの処理でisErrorがtrueになっている場合、つまり10文字以上の文字が入力されている場合はtrueが入ります。
それとtodoLengthZoroの関数をここで呼んでいます。

  const todoLengthZero = (): boolean => {
    if (todoText.length === 0) return true;
  }

こちらはtodoの文字が0と等しければtrueを返す関数です。
つまり0文字以上ではボタンにdisabledにtrueが入り、ボタンが押せなくなるのです。

こちらをまとめると

disabled = { 文字数が10文字以上の場合はtrue または 文字数が0文字と等しい場合はtrue }

になります!

ただこれだけだと、初期ロード時には0文字でもボタンがdisabledになりません。
なぜなら
todoLengthZeroの関数が呼ばれないからです。

こちらを改善するのがuseEffectです

  useEffect((): void => {
    todoLengthZero()
  }, [])

useEffectはマウント時に処理を走らせます。
ちなみにreturnを記載するとアンマウント時の処理になります。

つまり初期ロードが走りマウント時に関数が走るためdisabledが有効になります。

これでバリデーションの処理が完了になります。

最後にスタイルを少し整えましょう。

.heading {
  font-size: 30px;
  font-weight: bold;
}

.heading2 {
  font-size: 24px;
  font-weight: bold;

  margin-top: 20px;
}

.md10 {
  margin-top: 10px;
}


.inputArea {

  margin-top: 20px;

  .todoInputField {
    margin-right: 5px;
  }
}

.errorMessage {
  color: red;
}

スクリーンショット 2021-07-30 0.12.19.png

スクリーンショット 2021-07-30 0.12.38.png

こちらで初級編は終了になります。
次に応用編ですが今回のコードをリファクタリグして、保守性の高いコードに近づけていきます。

お疲れさまでした!

10
7
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
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?