React Hook Formの基本的な使い方まとめ (original) (raw)

React でフォームを扱うためのライブラリである react-hook-form についてまとめました。

この記事は以下の構成になっています。

react-hook-form とは、React でフォームを簡単に扱うためのライブラリ。

react-hook-form を使わずにフォームを実装する

react-hook-form を使わずに、記事のタイトルと本文を入力して送信したら表示されるという機能を作る。

実装の流れは以下の通り。

  1. Form コンポーネントと Article コンポーネントを作る
  2. Form と Article の親コンポーネントで、useState を使って、フォームに入力する値である title ・ content と、送信されて表示される値である articleTitle ・ articleContent を管理する
  3. onChangeを使ってフォームに入力できるようにする
  4. onSubmitを使ってフォームを送信できるようにする
  5. 送信された値を表示する

1. Form コンポーネントと Article コンポーネントを作る

記事のタイトルを入力する input と本文を入力する textarea をもつ Form コンポーネントを作る。

Form.jsx

export const Form = () => { return (

  form style={{ textAlign: 'center' }}
    div
      label htmlFor='title'タイトルlabel
      input type='text' id='title' 
    div
    div
      label htmlFor='content'本文label
      textarea id='content' rows={4} cols={40} 
    div
    div
      input type='submit' value='送信' 
    div
  form

) }

次に、送信された値を表示する Article コンポーネントを作る。

Article.jsx

export const Article = () => { return ( div style={{ textAlign: 'center' }} pタイトル:p p本文:p div ) }

これらの Form と Article を親のコンポーネントで呼び出す。

App.jsx

import { Form } from './Form' import { Article } from './Article'

function App() { return (

  Form 
  Article 

) }

export default App

現状は JSX を表示するだけ。

2. Form と Article の親コンポーネントで、useState を使って、フォームに入力する値である title ・ content と、送信されて表示される値である articleTitle ・ articleContent を管理する

次に App コンポーネントで、useState を使って、フォームに入力するタイトルである title と本文である content、送信された値である articleTitle と articleContent の state を管理する。

App.jsx

import { useState } from 'react' import { Form } from './Form' import { Article } from './Article'

function App() { const [title, setTitle] = useState('') const [content, setContent] = useState('') const [articleTitle, setArticleTitle] = useState('') const [articleContent, setArticleContent] = useState('')

return (

  Form title={title} content={content} 
  Article 

) }

export default App

Form の props として title と content を渡して、input と textarea の value 属性でそれぞれの値を表示されるようにする。

Form.jsx

export const Form = () => { return (

  form style={{ textAlign: 'center' }}
    div
      label htmlFor='title'タイトルlabel
      input type='text' id='title' value={title} 
    div
    div
      label htmlFor='content'本文label
      textarea id='content' rows={4} cols={40} value={content} 
    div
    div
      input type='submit' value='送信' 
    div
  form

) }

title と content は初期値のままなので現状は空文字で何も表示されない。

3. onChangeを使ってフォームに入力できるようにする

次に Form コンポーネントで、onChangeを使ってフォームに入力できるようにする。

onChange は子コンポーネントである Form で発火して state を変更するが、state を更新する関数は親コンポーネントにあるため、親の App.jsx でsetTitlesetContentを使った関数を作り、props として Form に渡して、onChangeで実行されるようにする。

App.jsx

import './App.css' import { Counter } from './Counter' import { AuthProvider } from './AuthContext' import { Form } from './Form' import { useState } from 'react' import { Article } from './Article'

function App() { const [title, setTitle] = useState('') const [content, setContent] = useState('') const [articleTitle, setArticleTitle] = useState('') const [articleContent, setArticleContent] = useState('')

const handleChangeTitle = (e) => { setTitle(e.target.value) } const handleChangeContent = (e) => { setContent(e.target.value) }

return (

  Form
    title={title}
    content={content}
    onChangeTitle={handleChangeTitle}
    onChangeContent={handleChangeContent}
  
  Article 

) }

export default App

Form.jsx

export const Form = ({ title, content, onChangeTitle, onChangeContent }) => { return (

  form style={{ textAlign: 'center' }}
    div
      label htmlFor='title'タイトルlabel
      input
        type='text'
        id='title'
        onChange={onChangeTitle}
        value={title}
      
    div
    div
      label htmlFor='content'本文label
      textarea
        id='content'
        rows={4}
        cols={40}
        onChange={onChangeContent}
        value={content}
      
    div
    div
      input type='submit' value='送信' 
    div
  form

) }

これでフォームに入力できるようになる。

4. onSubmitを使ってフォームを送信できるようにする

次に入力された値を送信する。

送信は Form コンポーネントでのonSubmitで行われるので、onChange同様、処理を関数として App から Form に渡しておく必要がある。

App.jsx

import { useState } from 'react' import { Form } from './Form' import { Article } from './Article'

function App() { const [title, setTitle] = useState('') const [content, setContent] = useState('') const [articleTitle, setArticleTitle] = useState('') const [articleContent, setArticleContent] = useState('')

const handleChangeTitle = (e) => { setTitle(e.target.value) } const handleChangeContent = (e) => { setContent(e.target.value) }

const handleSubmit = (e) => { e.preventDefault() setArticleTitle(title) setArticleContent(content) setTitle('') setContent('') } return (

  Form
    title={title}
    content={content}
    onChangeTitle={handleChangeTitle}
    onChangeContent={handleChangeContent}
    onSubmit={handleSubmit}
  
  Article 

) }

export default App

送信処理はhandleSubmitで実装する。処理の内容は以下の通り。

  1. フォームが送信されるとデフォルトの動作としてページがリロードされるため、e.preventDefault()でデフォルトの動作が起こらないようにする
  2. 送信されたtitlecontentsetArticleTitlesetArticleContentarticleTitlearticleContentに保存する
  3. フォームの値が必要ないのでsetTitlesetContentで初期化する

Form

export const Form = ({ title, content, onChangeTitle, onChangeContent, onSubmit, }) => { return (

  form onSubmit={onSubmit} style={{ textAlign: 'center' }}
    div
      label htmlFor='title'タイトルlabel
      input
        type='text'
        id='title'
        onChange={onChangeTitle}
        value={title}
      
    div
    div
      label htmlFor='content'本文label
      textarea
        id='content'
        rows={4}
        cols={40}
        onChange={onChangeContent}
        value={content}
      
    div
    div
      input type='submit' value='送信' 
    div
  form

) }

onSubmitイベントに props のonSubmitを渡す。

ここまででフォームの入力から送信までが完成。

5. 送信された値を表示する

最後にarticleTitlearticleContentを props として Article コンポーネントに渡して表示する。

App.jsx

import { useState } from 'react' import { Form } from './Form' import { Article } from './Article'

function App() { const [title, setTitle] = useState('') const [content, setContent] = useState('') const [articleTitle, setArticleTitle] = useState('') const [articleContent, setArticleContent] = useState('')

const handleChangeTitle = (e) => { setTitle(e.target.value) } const handleChangeContent = (e) => { setContent(e.target.value) }

const handleSubmit = (e) => { e.preventDefault() setArticleTitle(title) setArticleContent(content) setTitle('') setContent('') } return (

  Form
    title={title}
    content={content}
    onChangeTitle={handleChangeTitle}
    onChangeContent={handleChangeContent}
    onSubmit={handleSubmit}
  
  Article articleTitle={articleTitle} articleContent={articleContent} 

) }

export default App

Article.jsx

export const Article = ({ articleTitle, articleContent }) => { return ( div style={{ textAlign: 'center' }} pタイトル:{articleTitle}p p本文:{articleContent}p div ) }

titlecontentを Article に渡して表示するとフォームの入力がリアルタイムで表示されるので、articleTitlearticleContentに保存して渡している。

これでフォームの入力から送信して表示するまでの処理が完成。

react-hook-form を使う手順

次にこのフォームを react-hook-form を使って実装して違いを理解する。

react-hook-form での基本的はフォームの実装は以下の手順で行う。

  1. パッケージをインストール
  2. useForm をインポートして、useForm から必要なプロパティ、メソッドを取得する
  3. register で個々のフィールドを作成する
  4. formState でエラーメッセージを表示する
  5. onSubmit に送信処理を書く

1. パッケージをインストール

react-hook-form を使うにはパッケージが必要なので、以下のコマンドを実行してreact-hook-formをインストールする。

npm i react-hook-form

2. useForm をインポートして、useForm から必要なメソッド等を取得する

react-hook-form ではuseFormというカスタムフックを使ってフォームを実装する。

useFormによってフォームの状態管理やバリデーションのルールを指定できる。

フォームのコンポーネントは HookForm とする。

import { useForm } from 'react-hook-form'

export const HookForm = () => { const { register, handleSubmit, formState: { errors }, } = useForm()

return formform }

useForm から取得したのは以下。

3. register 関数でそれぞれのフィールドを作成する

react-hook-form では register を使って個々のフォームを作成する。

register には第一引数にフォームの name 属性を指定し、第二引数はそのフォームに対するバリデーションルールを設定する。

import { useForm } from 'react-hook-form'

export const HookForm = () => { const { register, handleSubmit, formState: { errors }, } = useForm()

return ( form style={{ textAlign: 'center' }} div label htmlFor='title'タイトルlabel input type='text' id='title' {...register('title', { required: 'タイトルは必須です' })}

  div
  div
    label htmlFor='content'本文label
    textarea
      id='content'
      rows={4}
      cols={40}
      {...register('content', { required: '本文は必須です' })}
    
  div
  div
    input type='submit' value='送信' 
  div
form

) }

ここではタイトルと本文を入力するフォームを実装し、入力必須のバリデーションを設定している。

4. formState でエラーメッセージを表示する

formStateerrors を使って、エラーメッセージを表示する。

import { useForm } from 'react-hook-form'

export const HookForm = () => { const { register, handleSubmit, formState: { errors }, } = useForm()

return ( form style={{ textAlign: 'center' }} div label htmlFor='title'タイトルlabel input type='text' id='title' {...register('title', { required: 'タイトルは必須です' })}

    {errors.title && p{errors.title.message}p}
  div
  div
    label htmlFor='content'本文label
    textarea
      id='content'
      rows={4}
      cols={40}
      {...register('content', { required: '本文は必須です' })}
    
    {errors.content && p{errors.content.message}p}
  div
  div
    input type='submit' value='送信' 
  div
form

) }

5. onSubmit 関数を作り、onSubmit イベントで実行する

react-hook-form では form の onSubmit でフォームの送信処理を行う。onSubmit が発火した時に、useForm からインポートした handleSubmit に実際の送信処理を渡したものをイベントハンドラとして実行する。

import { useState } from 'react' import { HookForm } from './HookForm' import { Article } from './components/Article'

function App() { const [article, setArticle] = useState({ title: '', content: '' })

const handleFormSubmit = (data) => { setArticle({ title: data.title, content: data.content }) }

return (

  HookForm onSubmit={handleFormSubmit} 
  Article articleTitle={article.title} articleContent={article.content} 

) }

export default App

HookForm.jsx

import { useForm } from 'react-hook-form'

export const HookForm = ({ onSubmit }) => { const { register, handleSubmit, formState: { errors }, } = useForm()

return ( form onSubmit={handleSubmit(onSubmit)} style={{ textAlign: 'center' }} div label htmlFor='title'タイトルlabel input type='text' id='title' {...register('title', { required: 'タイトルは必須です' })}

    {errors.title && p{errors.title.message}p}
  div
  div
    label htmlFor='content'本文label
    textarea
      id='content'
      rows={4}
      cols={40}
      {...register('content', { required: '本文は必須です' })}
    
    {errors.content && p{errors.content.message}p}
  div
  div
    input type='submit' value='送信' 
  div
form

) }

handleSubmit によってバリデーションが行われる。

バリデーションが成功すると、送信されたフォームのデータが onSubmit に渡され、onSubmit が実行される。

その他

フォームの値をリアルタイムで表示する

プレビューのような、フォームの値をリアルタイムで表示するにはwatchを使う。

先ほどの例のコードをwatchを使ってフォームの入力と同時にリアルタイムで表示されるようにする。

import { useState } from 'react' import { useForm } from 'react-hook-form' import { Article } from './components/Article'

export const HookForm = ({ onSubmit }) => { const [article, setArticle] = useState({ title: '', content: '' })

const { register, handleSubmit, formState: { errors }, watch, } = useForm()

const title = watch('title') const content = watch('content')

const handleFormSubmit = (data) => { setArticle({ title: data.title, content: data.content }) }

return (

  form
    onSubmit={handleSubmit(handleFormSubmit)}
    style={{ textAlign: 'center' }}
  
    div
      label htmlFor='title'タイトルlabel
      input
        type='text'
        id='title'
        {...register('title', { required: 'タイトルは必須です' })}
      
      {errors.title && p{errors.title.message}p}
    div
    div
      label htmlFor='content'本文label
      textarea
        id='content'
        rows={4}
        cols={40}
        {...register('content', { required: '本文は必須です' })}
      
      {errors.content && p{errors.content.message}p}
    div
    div
      input type='submit' value='送信' 
    div
  form
  Article title={title} content={content} 

) }

Article コンポーネントを HookForm と同階層に表示し、watchで監視している値を props として渡す。

フォームの送信処理は意味がなくなるが、フォームの入力を別の場所でリアルタイムに表示できる。

フォームの値を初期化する

フォームの値を初期化するにはresetを使う。

例のフォームで送信処理を行った後にresetでフォームの値を初期化する。

import { useForm } from 'react-hook-form' import { Article } from './components/Article' import { useState } from 'react'

export const HookForm = () => { const [article, setArticle] = useState({ title: '', content: '' })

const { register, handleSubmit, formState: { errors }, reset, } = useForm()

const handleFormSubmit = (data) => { setArticle({ title: data.title, content: data.content }) reset() }

return (

  form
    onSubmit={handleSubmit(handleFormSubmit)}
    style={{ textAlign: 'center' }}
  
    div
      label htmlFor='title'タイトルlabel
      input
        type='text'
        id='title'
        {...register('title', { required: 'タイトルは必須です' })}
      
      {errors.title && p{errors.title.message}p}
    div
    div
      label htmlFor='content'本文label
      textarea
        id='content'
        rows={4}
        cols={40}
        {...register('content', { required: '本文は必須です' })}
      
      {errors.content && p{errors.content.message}p}
    div
    div
      input type='submit' value='送信' 
    div
  form
  Article title={article.title} content={article.content} 

) }

handleFormSubmit内で、 を実行した後に、resetを実行することでフォームの値を初期化する。

UI ライブラリのコンポーネントを使う場合

外部の UI ライブラリのコンポーネントを使用するときに、registerが対応していない場合はControllerを使う。

Controllerは外部の UI ライブラリのコンポーネントを react-hook-form に統合するためのコンポーネント

Controllerを使う際は useForm からcontrolを取得してControllerで指定する必要がある。controlはフォームのフィールドを管理するオブジェクトで、値やバリデーションなどのフォームの状態を追跡する。

例として MaterialUI からフォームのコンポーネントを使うとする。(MaterialUI はインストールしてある前提)

import { useState } from 'react' import { useForm, Controller } from 'react-hook-form' import { TextField, Button } from '@mui/material' import { Article } from './components/Article'

export const HookForm = () => { const [article, setArticle] = useState({ title: '', content: '' })

const { handleSubmit, formState: { errors }, reset, control, } = useForm()

const handleFormSubmit = (data) => { setArticle({ title: data.title, content: data.content }) reset() }

return (

  form
    onSubmit={handleSubmit(handleFormSubmit)}
    style={{ textAlign: 'center' }}
  
    div
      Controller
        name='title'
        control={control}
        defaultValue=''
        rules={{ required: 'タイトルは必須です' }}
        render={({ field }) => (
          TextField
            {...field}
            label='タイトル'
            variant='outlined'
            error={!!errors.title}
            helperText={errors.title ? errors.title.message : ''}
            fullWidth
            margin='normal'
          
        )}
      
    div
    div
      Controller
        name='content'
        control={control}
        defaultValue=''
        rules={{ required: '本文は必須です' }}
        render={({ field }) => (
          TextField
            {...field}
            label='本文'
            variant='outlined'
            multiline
            rows={4}
            error={!!errors.content}
            helperText={errors.content ? errors.content.message : ''}
            fullWidth
            margin='normal'
          
        )}
      
    div
    div
      Button variant='contained' color='primary' type='submit'
        送信
      Button
    div
  form
  Article title={article.title} content={article.content} 

) }

Controllerで指定していることは以下。

renderにはfieldというオブジェクトを引数に受け取ってコンポーネントを返すコールバック関数を渡している。

fieldはフォームフィールドの値やイベントハンドラなどが含まれる react-hook-form が提供するオブジェクト。

react-hook-form のメリット

react-hook-form を使うと、主に以下のメリットがある。

フォームのデータを簡単に管理できる

react-hook-form を使わない場合は、useState を使ってフォームごとに state を作って、onChange のイベントハンドラで入力できるようにするが、react-hook-form を使う場合は register を使ってフォームを実装し、 handleSubmit で送信処理を書くだけ。

不要な再レンダリングを防げる

react-hook-form を使わない場合は、onChange によってテキストを 1 文字入力するたびに再レンダリングが発生するが、react-hook-form を使う場合は必要な時にフォームの状態状態が更新されるので余分な再レンダリングは発生しない。

バリデーションを簡単に書ける

react-hook-form を使わない場合は、バリデーションのメッセージも useState で管理する必要があるが、react-hook-form を使う場合は、バリデーションのルールを register で宣言的に書くことができ、エラーメッセージは formStateerrors で表示できる。