Closed5

GPT-4にReactのコードを書かせてみる

Hidetoshi OtaHidetoshi Ota

ReactでFormを作って

項目は以下

  • email
  • password

言語はTypeScript、ライブラリはReact Hook Form、zod、tailwindを使って
submit時にAPIをpostして、400エラーが発生したらFormにエラーを表示して


Form.tsx

import React from 'react';
import { useForm, SubmitHandler, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

type FormData = z.infer<typeof schema>;

export const Form: React.FC = () => {
  const {
    register,
    handleSubmit,
    control,
    formState: { errors },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit: SubmitHandler<FormData> = async (data) => {
    try {
      const response = await fetch('YOUR_API_ENDPOINT', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (!response.ok && response.status === 400) {
        console.error('Error 400: Bad Request');
        // フォームにエラーを表示する処理をここに追加します
      } else {
        // 成功した場合の処理をここに追加します
      }
    } catch (error) {
      console.error('Error:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <div>
        <label htmlFor="email" className="block text-sm font-medium">
          Email
        </label>
        <input
          id="email"
          type="email"
          {...register('email')}
          className={`w-full p-2 border ${
            errors.email ? 'border-red-600' : 'border-gray-300'
          }`}
        />
        {errors.email && (
          <p className="text-red-600 text-sm">{errors.email.message}</p>
        )}
      </div>
      <div>
        <label htmlFor="password" className="block text-sm font-medium">
          Password
        </label>
        <input
          id="password"
          type="password"
          {...register('password')}
          className={`w-full p-2 border ${
            errors.password ? 'border-red-600' : 'border-gray-300'
          }`}
        />
        {errors.password && (
          <p className="text-red-600 text-sm">{errors.password.message}</p>
        )}
      </div>
      <button
        type="submit"
        className="w-full p-2 bg-blue-600 text-white font-semibold"
      >
        Submit
      </button>
    </form>
  );
};
Hidetoshi OtaHidetoshi Ota

パスワードは英大文字、英小文字、数字、記号を必須にして


const schema = z.object({
  email: z.string().email(),
  password: z
    .string()
    .refine(
      (value) => /[A-Z]/.test(value),
      'Password must contain at least one uppercase letter'
    )
    .refine(
      (value) => /[a-z]/.test(value),
      'Password must contain at least one lowercase letter'
    )
    .refine(
      (value) => /[0-9]/.test(value),
      'Password must contain at least one number'
    )
    .refine(
      (value) => /[\W_]/.test(value),
      'Password must contain at least one special character'
    )
    .min(8, 'Password must be at least 8 characters long'),
});
Hidetoshi OtaHidetoshi Ota

inputを汎用コンポーネント化してForm.tsxから使って


Input.tsx

import React from 'react';
import { UseFormRegister, FieldError } from 'react-hook-form';

type InputProps = {
  id: string;
  type: string;
  label: string;
  register: UseFormRegister<any>;
  error?: FieldError;
};

export const Input: React.FC<InputProps> = ({ id, type, label, register, error }) => {
  return (
    <div>
      <label htmlFor={id} className="block text-sm font-medium">
        {label}
      </label>
      <input
        id={id}
        type={type}
        {...register(id)}
        className={`w-full p-2 border ${
          error ? 'border-red-600' : 'border-gray-300'
        }`}
      />
      {error && <p className="text-red-600 text-sm">{error.message}</p>}
    </div>
  );
};

Form.tsx

import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Input } from './Input';

// ... schema, FormData, and onSubmit ...

export const Form: React.FC = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <Input
        id="email"
        type="email"
        label="Email"
        register={register}
        error={errors.email}
      />
      <Input
        id="password"
        type="password"
        label="Password"
        register={register}
        error={errors.password}
      />
      <button
        type="submit"
        className="w-full p-2 bg-blue-600 text-white font-semibold"
      >
        Submit
      </button>
    </form>
  );
};
Hidetoshi OtaHidetoshi Ota

InputコンポーネントをFormFieldコンポーネントとInputコンポーネントに分割して


Input.tsx

import React from 'react';

type InputProps = {
  id: string;
  type: string;
  register: any;
  hasError: boolean;
};

export const Input: React.FC<InputProps> = ({ id, type, register, hasError }) => {
  return (
    <input
      id={id}
      type={type}
      {...register}
      className={`w-full p-2 border ${
        hasError ? 'border-red-600' : 'border-gray-300'
      }`}
    />
  );
};

FormField.tsx

import React from 'react';
import { FieldError } from 'react-hook-form';
import { Input } from './Input';

type FormFieldProps = {
  id: string;
  type: string;
  label: string;
  register: any;
  error?: FieldError;
};

export const FormField: React.FC<FormFieldProps> = ({ id, type, label, register, error }) => {
  return (
    <div>
      <label htmlFor={id} className="block text-sm font-medium">
        {label}
      </label>
      <Input id={id} type={type} register={register} hasError={!!error} />
      {error && <p className="text-red-600 text-sm">{error.message}</p>}
    </div>
  );
};

Form.tsx

import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { FormField } from './FormField';

// ... schema, FormData, and onSubmit ...

export const Form: React.FC = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <FormField
        id="email"
        type="email"
        label="Email"
        register={register('email')}
        error={errors.email}
      />
      <FormField
        id="password"
        type="password"
        label="Password"
        register={register('password')}
        error={errors.password}
      />
      <button
        type="submit"
        className="w-full p-2 bg-blue-600 text-white font-semibold"
      >
        Submit
      </button>
    </form>
  );
};
Hidetoshi OtaHidetoshi Ota

コンポーネントファイル内の定数、関数、型をファイル分割して


src/types.ts

import { z } from 'zod';

export const schema = z.object({
  email: z.string().email(),
  password: z
    .string()
    .refine(
      (value) => /[A-Z]/.test(value),
      'Password must contain at least one uppercase letter'
    )
    .refine(
      (value) => /[a-z]/.test(value),
      'Password must contain at least one lowercase letter'
    )
    .refine(
      (value) => /[0-9]/.test(value),
      'Password must contain at least one number'
    )
    .refine(
      (value) => /[\W_]/.test(value),
      'Password must contain at least one special character'
    )
    .min(8, 'Password must be at least 8 characters long'),
});

export type FormData = z.infer<typeof schema>;

src/utils.ts

現在、Form.tsx にユーティリティ関数はありませんが、将来的に追加する場合は、このファイルに追加します。

src/constants.ts

現在、Form.tsx に定数はありませんが、将来的に追加する場合は、このファイルに追加します。

Form.tsx

import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { FormField } from './FormField';
import { FormData, schema } from './types';

// ... onSubmit and Form component ...
このスクラップは2023/03/16にクローズされました