TERUSIの技術勉強まとめ(コーディング編)

新規作成画面作成(画面とvalidation機能のみ)

作成日: 2023-10-08T08:59:55.892Z

はじめに

前回はホーム画面を作りましたね。今回は新規登録作成の画面を作成します。
機能が多いのでAPIを叩くということは今回はしません。
ひとまず画面を作ってバリデーション機能まで作ることをゴールとします。

完成イメージ


見た目はこのような感じで

バリデーション機能がついていて

正しい形式で入力すると

このようにgoogleのデベロッパーツールのconsoleにて入力情報が出てくるところまでやりましょう。

packege install

早速本日使うpackegeをinstallしていきましょう。

yarn add react-hook-form yarn add @hookform/resolvers yup

今回のpackegeはvalidation系のものになっています。validationは何かというと私のイメージでは入力形式のことでメールアドレスの形式が合っているか必須項目にテキストが入っているかなどのチェックをするためのものになります。

ディレクトリ構成


大分、ファイル数も多くなってきました。
前回と変わったところが何点かあります。

  • rules.tsを作成しここにバリデーション機能のコードを書く
  • 以前「home」ディレクトリにあった「routingButton」を共通部品として保存する「components」に移動し「home」ディレクトリを削除

です。この点が変わったのでよろしくお願いします。尚、コードの中身自体は前回と同じなのでその辺は心配いりません。vscodeを使っている人は特に心配はいらず、ドラッグ&ドロップで移動すれば自動でimportのパスを書き換えてくれます。
ちなみに今回は、「auth」フォルダの「signup」フォルダの中を中心に追加・編集していきます。また、共通のcomponentも出てくるので「components」フォルダの中にも追加していきます。

実装開始

1 文言ファイルの設定

まずは文言ファイルの設定です。ja.tsを開いてください。今回は全体コードを記述しておきますが、次回からは長くなってしまうので追加する部分のみを記述します。どこに追加して良いかわからない人もいると思うので今回はそのようにしています。

// eslint-disable-next-line import/no-anonymous-default-export
export default {
    home:{
        title:"TODO APP",
        description:"自分だけのTODOを作成しよう!",
        buttonTitle1:"新規アカウント作成",
        buttonTitle2:"ログイン",
    },
    signup:{
        title:"新規登録",
        instruction:"名前、メールアドレス、パスワードを入力してください。",
        validationCheck:{
            required:"この項目は必須です",
            email:"メールアドレスの形式が違います",
            password:"パスワードが短いです"
        },
        attribute:{
            name:{
                labelName:"name",
            },
            email:{
                labelName:"email",
                typeName:"email",
            },
            password:{
                labelName:"password",
                typeName:"password",
            }
        },
        buttonTitle:"新規登録する",
        gobackBotton:"ホームに戻る"
    }
}

2 リンクファイルの設定

今回はホームに戻るという処理をするためにホームのパスをリンクファイルに追加します。linkName.tsを開いてください。

// eslint-disable-next-line import/no-anonymous-default-export
export default{
    signup:"/auth/signup",
    login:"/auth/login",
    home:"/"
}

3 titleの部分を作成する

title部分というのは「新規登録」という部分と「名前、メールアドレス。。。」という部分です。ここを一つのcomponentとします。
ログインの部分でも用いるので「components」に保存していきましょう。「component」フォルダの下に「loginTitle」というフォルダ名にしましょう。そしてindex.tsxをその下に作成します。前回とほぼ変わらないコードなので説明はあまりしません。実際のコードは以下のようになります。

interface props{
    title:string;
    instruction:string;
}
const LoginTitle = ({title,instruction}:props) => {
    return (
        <div>
            <h1>{title}</h1>
            <p>{instruction}</p>
        </div>
    );
}

export default LoginTitle;

4 フォーム部分を作成する

4.1 フォームのcomponentを作成

編集するといった「signup」ディレクトリの下に「signupForm」ディレクトリを作りましょう。今回はstyleも振るのでindex.tsxstyle.css.tsを用意しておきます。基本的にpageになるのはpage.tsxでcomponentはindex.tsxでstyleに関してはstyle.css.tsです。今後は細かく言わないので今のうちに覚えておいてください。では、「signupForm」ディレクトリのコードを書いていきます。

'use client'

import InputForm from "@/app/components/inputForm";
import ja from "@/shared/ja";
import { signupValidation } from "@/shared/rules";
import { signupType } from "@/shared/type";
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
import styles from "./style.css";
import SubmitButton from "@/app/components/submitButton";
import RoutingButton from "@/app/components/routingButton";
import linkName from "@/shared/linkName";

const SignupForm = () => {
    const { register, 
            handleSubmit,
            formState:{errors}
        } = useForm<signupType>({
        resolver: yupResolver(signupValidation),
    });
    const onSubmit=async(data:signupType)=>{
        console.log(data);
    };
    handleSubmit(onSubmit);
    return (
        <div>
            <div className={styles.inputText}>
                <InputForm
                    labelName={ja.signup.attribute.name.labelName}
                    register={register('name')}
                    error={'name' in errors}
                    helperText={errors.name?.message} 
                    typeName={undefined}                
                />
            </div>
            <div className={styles.inputText}>
                <InputForm
                    labelName={ja.signup.attribute.email.labelName}
                    register={register('email')}
                    error={'email' in errors}
                    helperText={errors.email?.message} 
                    typeName={ja.signup.attribute.email.typeName}                
                />
            </div>
            <div className={styles.inputText}>
                <InputForm
                    labelName={ja.signup.attribute.password.labelName}
                    register={register('password')}
                    error={'password' in errors}
                    helperText={errors.password?.message} 
                    typeName={ja.signup.attribute.password.typeName}                
                />
            </div>
            <div className={styles.inputButton}>
                <SubmitButton
                    submitEvent={handleSubmit(onSubmit)}
                    buttonTitle={ja.signup.buttonTitle}
                />
            </div>
            <div className={styles.inputButton}>
                <RoutingButton
                    linkName={linkName.home}
                    linkTitle={ja.signup.gobackBotton}
                />
            </div>
        </div>
    );
}

export default SignupForm;

いくつかのcomponentを作っていないのでエラーがたくさんあると思いますが、気にしないでください。後に作るので。
onSubmit関数はクリックされた後の処理の関数です。今のところは入力したものをconsoleに出すということしかしていませんが、後にAPIを叩く処理をここに書いていきます。ここではreact-hooks-formsを使ってvalidation機能の基盤を作成しています。そして、これから作るTextfieldにpropsを渡します。まだ、入力要素である、nama,email,passwordのそれぞれのルールを設定していないので次のsectionでルールを記述していこうと思います。
ちなみに上に「'use client'」と書いてあるのはこれはCSR(クライアントサイドレンダリング)を宣言しています。reactのhooksを使う場合はCSRでないといけません。hooksを使うであったりクリック操作をするなどクライアント側が操作するものはCSRで宣言しましょう。appルータではデフォルトがSSR(サーバーサイドレンダリング)となっています。ここは覚えておきましょう。

4.2 validation rulesの設定

ということでルールを決めていきましょう。今回はyupというのを使ってみました。使わないとしたら、メールアドレスの形式を正規表現で自ら作らないといけないのでちょっと頭が痛いです。その作業がなくなるのは個人的に嬉しかったので使ってみました。ではrules.tsに記述していきましょう。

import * as yup from 'yup';
import ja from './ja';
// 新規登録のバリデーションチェック
export const signupValidation = yup.object({
    name: yup.string().required(ja.signup.validationCheck.required),
    email: yup
        .string()
        .required(ja.signup.validationCheck.required)
        .email(ja.signup.validationCheck.email),
    password: yup
        .string()
        .required(ja.signup.validationCheck.required)
        .min(8, ja.signup.validationCheck.password)
});

validation機能のルールをここで作りました。各項目を必須とし、 メールアドレスはemail()にて形式をチェック、パスワードは最低8文字に設定しています。

4.3 TextFieldの作成

ではTextFieldをmuiを用いて作っていきたいと思います。
「components」フォルダに「inputForm」というフォルダを作成します。その下でコードを書いていきましょう。

import { signupType } from "@/shared/type";
import { TextField } from "@mui/material";
import { UseFormRegisterReturn } from "react-hook-form";
interface props{
    labelName:string;
    register:UseFormRegisterReturn<"name"|"email"|"password">;
    error:boolean;
    typeName:string|undefined;
    helperText:string|undefined;
}
const InputForm = ({labelName,register,error,typeName,helperText}:props) => {
    return (
        <TextField
            required
            {...register}
            label={labelName}
            error={error}
            type={typeName}
            helperText={helperText}
        />
    );
}

export default InputForm;

muiのTextFieldを使っています。ドキュメントを見るとわかるのですが、errorの属性がtrueだった場合は赤くなり同時にhelperTextには検知したエラーのメッセージが出されます。個人で開発するときはかなり楽に実装ができるので嬉しい限りです。typeにemailやpasswordを入れるとログインで使っているような入力欄になります。passwordが特にわかりやすいかと思います。もう少しこだわるとしたら目のアイコンを入れて見れるようにしたり見れないようにしたりといった設定もできるのですが、今回は実装しません。ドキュメントにはそのような実装方法も載っているのでぜひアレンジしても良いかもですね。
ドキュメント

4.4 submitボタンの作成

こちらもmuiを使ってボタンを作成していきます。
「components」フォルダに「submitButton」というフォルダを作りましょう。
ではコードを書いていきます。

import { Button } from "@mui/material";
import { BaseSyntheticEvent } from "react";

interface props{
    submitEvent:(e?: BaseSyntheticEvent<object, any, any> | undefined)=>Promise<void>;
    buttonTitle:string;
}
const SubmitButton = ({submitEvent,buttonTitle}:props) => {
    return (
        <Button
            variant="outlined"
            onClick={submitEvent}
        >
            {buttonTitle}
        </Button>
    );
}

export default SubmitButton;

ここではmuiを用いてボタンを作成しておりクリックの際は先程validationチェックが通ったらonSubmit関数を発火させる関数を宣言しています。

4.5 signupFormのstyle

styleをあてていなかったので「signupForm」フォルダの中のstylesheetファイルでstyleをあてておきましょう。

import { style } from "@vanilla-extract/css";

const styles={
    inputText:style({
        margin:"20px"//ボタンとボタンの間を20pxくらい開けておく。marginBottomでも良いかも。
    }),
    inputButton:style({
        margin:"25px"//ボタンとボタンの間を20pxくらい開けておく。marginBottomでも良いかも。
    }),

}
export default styles;

4.6 page.tsxおよびそのstyle

描画されるファイルであるpage.tsxを記述していきます。フォルダは「signup」です。

import LoginTitle from "@/app/components/loginTitle";
import ja from "@/shared/ja";
import SignupForm from "./signupForm";
import styles from "./style.css";

const Signup = () => {
    return (
        <main className={styles.containar}>
            {/*新規登録のタイトル*/}
            <LoginTitle
                title={ja.signup.title}
                instruction={ja.signup.instruction}
            />
            {/*ログインのフォーム*/}
            <SignupForm/>
        </main>
    );
}

export default Signup;

続いてstyleをふります。

import { style } from "@vanilla-extract/css";

const styles={
    containar:style({
        width:"75%",//長さはブラウザの横の75% 
        margin:"150px auto",// 上に50pxで以外は中央よせに,
        textAlign:"center",//文字を中央に寄せる(デフォルトは中央添え)
    }),
}
export default styles;

これで実装は新規登録の画面の実装は終わりです。

layout.tsxの修正

buildをしたらlayout.tsxのところで引っかかったので、修正します。

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Todo',
  description: 'あなただけのTodoアプリです。',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ja">
      <body style={{margin:0}}>{children}</body>
    </html>
  )
}

これでbuildが通るかと思います。

次回

ログインの画面(APIを叩くのは画面ができてからにしようと思います。)