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

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

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

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

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

![20220630-153625.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2385943/39ea1883-c2c6-71a7-fc38-8fe2afab76df.png)

## 実装
上記の動きを実装すると、以下のようなコードとなります。
```tsx
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 を生成
```tsx
  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 を定義
```tsx
  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`には配列が入っているので、その添字に`map`の`index`を使用してあげることで、それぞれの要素にrefを適用させることができます。

#### ③ (コンポーネント側)forwardRefを使用して、refをpropsで渡す
```tsx
  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要素に干渉する
```tsx
  const onClickFocus = () => {
    // 最後のinputにfocusする
    fruitRefs.current[fruits.length - 1].current?.focus()
  }
```
例えば、最後のinputItemのinput要素にフォーカスを当てたい場合は、`fruitRefs.current`の添字を調整して参照します。


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

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