はじめに
Reactで map を用いて繰り返しDOMを生成したものに対して useRef
を適用したいときのTipsです。
概要
例えば input要素をコンポーネント化してリストにし、その数が動的に変わる かつ その中のinputにアクセスしたい場合は、それぞれのコンポーネントのref指定を少し工夫をする必要があります。
今回は、
- focusボタンを押すと、最後のinput要素にフォーカスされるようにする
- 削除ボタンを押すと、対象のinput要素をクリアしてフォーカスする
というのを例に挙げて、mapでDOMを生成したものに対してそれぞれuseRef
を適用する方法を解説します。
実装
上記の動きを実装すると、以下のようなコードとなります。
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
には配列が入っているので、その添字にmap
のindex
を使用してあげることで、それぞれの要素に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を用いて開発をされている方の一助となれば幸いです!