16
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【React】カスタムフックで大量のuseCallbackを楽に記述する

Last updated at Posted at 2020-08-28

Material UIを使ってフォームを作成するとき、テキストフィールドなどはコンポーネント化しちゃいますよね。

親コンポーネント(入力フォームのページ)ではuseStateで入力値を管理する。

その時に、子コンポーネント(テキストフィールド)にonChange用の関数を渡すので、useCallbackでsetState関数をメモ化して上げる必要があります。

src/components/TextInput.tsx
import React from 'react'
import TextField from '@material-ui/core/TextField'

interface TextInputProps {
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
  value: string
}

const TextInput = (props: TextInputProps) => {
  return (
    <TextField
      value={props.value}
      type={"text"}
      onChange={props.onChange}
    />
  )
}

export default TextInput
src/index.tsx
import React, {FC, useEffect, useState} from 'react';
import TextInput from 'src/components/TextInput'

const Hoge: FC () => {
  const [name, setName] = useState<string>('');
  const inputName = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    setName(event.target.value)
  },[setName])

  return (
    <form>
      <TextInput onChange={inputName} value={name} />
    </form>
  )
}

このようなテキストフィールドが1つならいいけど、複数あると、毎回useCallbackを作るのがバリ面倒です。

なので、useCallbackでメモ化された関数を作るカスタムフックを作成しました。

src/lib/customHooks.ts
import React, {useCallback, SetStateAction, Dispatch} from 'react'

export const useStringChangeEventCallback = (update: Dispatch<SetStateAction<string>>) => {
  return useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    update(event.target.value)
  },[update])
}

こうして使います。

src/index.tsx
import React, {FC, useEffect, useState} from 'react';
import TextInput from 'src/components/TextInput'
import {createStringChangeEventCallback} from 'src/lib/customHooks'

const Hoge: FC () => {
  const [name, setName] = useState<string>(''),
    [address, setAddress] = useState<string>(''),
    [companyName, setCompanyName] = useState<string>('');

  return (
    <form>
      <TextInput
        onChange={useStringChangeEventCallback(setName)}
        value={name}
      />
      <TextInput
        onChange={useStringChangeEventCallback(setAddress)}
        value={address}
      />
      <TextInput
        onChange={useStringChangeEventCallback(setCompanyName)}
        value={companyName}
      />
    </form>
  )
}

これで入力フィールドがたくさん必要なフォームだとしても、スッキリ書けますね!!!

数値型の入力フィールドならこんな関数を用意しておくのもアリ。

src/lib/customHooks.ts
export const useNumberChangeEventCallback = (update: Dispatch<SetStateAction<number>>) => {
  return useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    if (/^[0-9]+$/.test(event.target.value)) {
      // event.target.valueは文字列型なので、数値型に変換する
      update(Number(event.target.value))
    }
  }, [update])
}

2020/09/19追記

Reactコンポーネント以外で useCallback などのHooksを使う場合、eslintに react-hooks/rules-of-hooks のルールを入れているとエラーになります。
縛りを緩めることになりますが、この方法を採用するなら、lintのルールから抜いてください。

2020/10/20追記

関数名を修正。
そもそもカスタムフックを作るときは use~~~ という関数名で作れば、上記lintで怒られることもありませんでした。
エラー文とドキュメントはよく読もう。(自戒)

16
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?