見出し画像

Next.jsでのフォームまわりの処理をメモしておく



こんにちは。

React(Next.js)でのフォーム処理は一般的なタスクですね。
フォームによっては「あれ?どうするんだっけ?」ということがよくあるので備忘録としてメモしておきます。
多くのサンプルはコンポーネント化されていないことが多いですが、今回は各パーツをしっかりとコンポーネント化して進めてみます。

1.text

'use client'

import React from 'react'

//---------------------------------------------
// props
//---------------------------------------------
export type Props = {
  type: 'text' | 'password' | 'number' | 'email' | 'tel'
  name: string
  value: string
  placeholder?: string
  disabled?: boolean
  min?: number
  max?: number
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
}

//---------------------------------------------
// component
//---------------------------------------------
export const InputText = ({ type, name, value, placeholder, disabled, min, max, onChange }: Props) => {
  return (
    <input
      type={type}
      name={name}
      value={value}
      placeholder={placeholder}
      disabled={disabled}
      minLength={min}
      maxLength={max}
      onChange={onChange}
    />
  )
}

2.textarea

'use client'

import React from 'react'

//---------------------------------------------
// props
//---------------------------------------------
export type Props = {
  name: string
  value: string
  placeholder?: string
  disabled?: boolean
  min?: number
  max?: number
  rows?: number
  onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
}

//---------------------------------------------
// component
//---------------------------------------------
export const InputTextarea = ({ name, value, placeholder, disabled, min, max, rows = 8, onChange }: Props) => {
  return (
    <textarea
      name={name}
      value={value}
      placeholder={placeholder}
      disabled={disabled}
      minLength={min}
      maxLength={max}
      rows={rows}
      onChange={onChange}
    />
  )
}

3.Radio

'use client'

import React from 'react'

//---------------------------------------------
// props
//---------------------------------------------
export type Props = {
  name: string
  value: string
  id: string
  checked: boolean
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
}

//---------------------------------------------
// component
//---------------------------------------------
export const InputRadio = ({ name, id, value, checked, onChange }: Props) => {
  return (
    <label htmlFor={id}>{value}</label>
    <input type={'radio'} name={name} id={id} value={value} checked={checked} onChange={onChange} />
  )
}

4.Check

'use client'

import React from 'react'

//---------------------------------------------
// props
//---------------------------------------------
export type Props = {
  name: string
  value: string
  id: string
  checked: boolean
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
}

//---------------------------------------------
// component
//---------------------------------------------
export const InputCheck = ({ name, id, value, checked, onChange }: Props) => {
  return (
    <label htmlFor={id}>{value}</label>
    <input type={'checkbox'} id={id} name={name} value={value} checked={checked} onChange={onChange} />
  )
}

5.File

'use client'

import React from 'react'

//---------------------------------------------
// props
//---------------------------------------------
export type Props = {
  name: string
  id: string
  selectedImage: File | null
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

//---------------------------------------------
// component
//---------------------------------------------
export const InputImage = ({ name, id, selectedImage, onChange }: Props) => {
  return (
    <>
      <Input type={'file'} accept={'image/*'} name={name} id={id} onChange={onChange} />
      <Label htmlFor={id}>画像アップ</Label>
      {selectedImage && (
        <div>
          <p>Selected Image:</p>
          <img src={URL.createObjectURL(selectedImage)} alt="Selected" width={200} />
        </div>
      )}
    </>
  )
}

6.各パーツをつかうとき

'use client'

import React, { ChangeEvent, useState } from 'react'
import { InputText } from '@/components/InputText/InputText'
import { InputTextarea } from '@/components/InputTextarea/InputTextarea'
import { InputRadio } from '@/components/InputRadio/InputRadio'
import { InputCheck } from '@/components/InputCheck/InputCheck'
import { InputImage } from '@/components/InputImage/InputImage'

//---------------------------------------------
// component
//---------------------------------------------
export const Form = ({ datas }: Props) => {

  const [data, setData] = useState<{
    text: string;
    textarea: string;
    check: { name: string; checked: boolean }[];
    radio: { name: string; checked: boolean }[];
    image: File | null;
  }>({
    text: '',
    textarea: '',
    check: [],
    radio: [],
    image: null
  })

  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {

    // 各inputのname / value / checked(true or false)が取得できる
    const target = e.target as HTMLInputElement;
    const { name, value, checked } = target;

    // 各input=nameで処理を分岐
    if(name === 'text') {
      // ...
    }

    if(name === 'textarea') {
      // ...
    }

    if(name === 'radio') {
      // ...
    }

    if(name === 'checkbox') {
      // ...
    }

    if(name === 'image') {
      try {
        const file = event.target.files && event.target.files[0];
        if (file) {
          // ファイル入力されたときの処理をする
        }
      } catch (error) {
          // エラーのときの処理をする
      }
    }
  }

  return (
    <form>
      <InputText
        type={'text'}
        name={'text'}
        value={data.text}
        placeholder={'文章を入力する'}
        onChange={handleChange}
      />
      <InputTextarea
        name={'textarea'}
        value={data.textarea}
        placeholder={'文章を入力する'}
        onChange={handleChange}
      />
      {data.radio.map((radio, index) => {
        return (
          <InputRadio
            name={'radio'}
            id={`${radio}_${index}`}
            value={radio.name}
            checked={radio.checked}
            onChange={handleChange}
          />
        )
      })}
      {data.check.map((check, index) => {
        return (
          <InputCheck
            name={'check'}
            id={`${check.name}_${index}`}
            value={check.name}
            checked={check.checked}
            onChange={handleChange}
          />
        )
      })}
      <InputImage
        name={'images'}
        id={'images'}
        selectedImage={data.image}
        onChange={handleImageChange}
      />
      <button type={'submit'}>送信</button>
    </form>
  )
}

よく忘れるので、今回ここにメモしておきます。

この記事が気に入ったらサポートをしてみませんか?