Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
9
Help us understand the problem. What is going on with this article?
@torahack

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

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で怒られることもありませんでした。
エラー文とドキュメントはよく読もう。(自戒)

9
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
torahack
インフラエンジニア3年→Webフロントエンジニア1年目 Python(スクレイピング, Flask, Django) Javascript(Node.js/React) AWS(Lambda, DynamoDB, API Gatewayなどサーバーレス) Firebase全般

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
9
Help us understand the problem. What is going on with this article?