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

## 前提知識

- 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/825833/1e2c695c-34bf-95d7-9278-0a2cfff0114a.png)
こちらが表示されたかと思います。
では簡単に説明していきます。

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

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

```tsx
const [ todoText, setTodoText ] = useState("");
```
こちらでstateとして定義しています。
初期値は空文字列なのでinputの中身はplaceholderが表示されている状態です。

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


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

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

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

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


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

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

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

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

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

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

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

```tsx
  setTodos(newArrayTodos);
  setTodoText("");
```

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

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

```tsx
      <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が登録された場合は

```tsx
  {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>
    </>
  );
};

```

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

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

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

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

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

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

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

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

```tsx
  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を入力します。


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

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

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

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

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

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

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

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

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

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

こちらをまとめると

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

になります！

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

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


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

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

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

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

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

```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;
  }
}

.errorMessage {
  color: red;
}

```

![スクリーンショット 2021-07-30 0.12.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/825833/18a17af7-d668-f4c3-1554-8a708c028488.png)

![スクリーンショット 2021-07-30 0.12.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/825833/2ce8133c-ecad-18c7-5399-453531876263.png)

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

お疲れさまでした！