70
38

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 1 year has passed since last update.

【React】複数useRefを指定するときのTips

Posted at

はじめに

Reactで map を用いて繰り返しDOMを生成したものに対して useRef を適用したいときのTipsです。

概要

例えば input要素をコンポーネント化してリストにし、その数が動的に変わる かつ その中のinputにアクセスしたい場合は、それぞれのコンポーネントのref指定を少し工夫をする必要があります。

今回は、

  • focusボタンを押すと、最後のinput要素にフォーカスされるようにする
  • 削除ボタンを押すと、対象のinput要素をクリアしてフォーカスする

というのを例に挙げて、mapでDOMを生成したものに対してそれぞれuseRefを適用する方法を解説します。

20220630-153625.png

実装

上記の動きを実装すると、以下のようなコードとなります。

import { useRef, createRef, forwardRef, RefObject } from 'react'

export default function App() {
  const fruits = ['apple', 'banana', 'orange', 'lemon', 'grape']
  const fruitRefs = useRef<RefObject<HTMLInputElement>[]>([])
  
  fruits.forEach((_, index) => {
    fruitRefs.current[index] = createRef<HTMLInputElement>()
  })

  const onClickFocus = () => {
    // 最後のinputにfocusする
    fruitRefs.current[fruits.length - 1].current?.focus()
  }

  const InputItem = forwardRef(({ fruit }: { fruit : string }, ref) => {
    const inputRef = ref as React.MutableRefObject<HTMLInputElement>
    const onClickDelete = () => {
      inputRef.current.value = ''
      inputRef.current.focus()
    }
    return (
      <>
        <input type="text" ref={inputRef} defaultValue={fruit} />
        <button type="button" onClick={onClickDelete}>削除</button>
      </>
    )
  })

  return (
    <div className="App">
      <button type="button" onClick={onClickFocus}>focus</button>
      {fruits.map((fruit, index) => (
        <div className="wrapper" key={fruit}>
          <InputItem ref={fruitRefs.current[index]} fruit={fruit} />
        </div>
      ))}
    </div>
  )
}

解説

① mapで繰り返す分だけ、 useRef を生成

  const fruits = ['apple', 'banana', 'orange', 'lemon', 'grape']
  const fruitRefs = useRef<RefObject<HTMLInputElement>[]>([])
  
  fruits.forEach((_, index) => {
    fruitRefs.current[index] = createRef<HTMLInputElement>()
  })

ここでは、fruitRefsという大枠のuseRefに空配列([])を定義しておいて、データの数分ループしてその配列の中にさらにrefを定義していっています。

② mapで生成する要素に対して ref を定義

  return (
    <div className="App">
      <button type="button" onClick={onClickFocus}>focus</button>
      {fruits.map((fruit, index) => (
        <div className="wrapper" key={fruit}>
          <InputItem ref={fruitRefs.current[index]} fruit={fruit} />
        </div>
      ))}
    </div>
  )

fruitRefs.currentには配列が入っているので、その添字にmapindexを使用してあげることで、それぞれの要素にrefを適用させることができます。

③ (コンポーネント側)forwardRefを使用して、refをpropsで渡す

  const InputItem = forwardRef(({ fruit }: { fruit : string }, ref) => {
    const inputRef = ref as React.MutableRefObject<HTMLInputElement>
    const onClickDelete = () => {
      inputRef.current.value = ''
      inputRef.current.focus()
    }
    return (
      <>
        <input type="text" ref={inputRef} defaultValue={fruit} />
        <button type="button" onClick={onClickDelete}>削除</button>
      </>
    )
  })

forwardRefを使用すると、子コンポーネントにpropsでrefを渡すことができます。
そして渡されたrefを、コンポーネント内の指定したい要素に適用してあげます。

この段階で、コンポーネント内であればrefでinput要素にアクセスすることが可能です。
(上記では型定義のために、一度inputRefに代入しています)

④ 親コンポーネントから子コンポーネントのinput要素に干渉する

  const onClickFocus = () => {
    // 最後のinputにfocusする
    fruitRefs.current[fruits.length - 1].current?.focus()
  }

例えば、最後のinputItemのinput要素にフォーカスを当てたい場合は、fruitRefs.currentの添字を調整して参照します。

最後に

refを適用する要素の数が動的に変わるような場合のTipsでした。
仕組みを見てみるとそんなに複雑なことではないのですが、いざ実装をするとなると意外と「どうするんだろう?」と迷ってしまう部分かと思います。(私がそうでした)

Reactを用いて開発をされている方の一助となれば幸いです!

70
38
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
70
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?