困りごと
ReactでuseState管理されているinput要素が増えた時に、その増えたinput要素にフォーカスを当てたい、というニーズは結構あるように思います。
Androidは比較的素直かと思いますが、(少なくともiOS16.6の)iOSのSafariではフォーカスは当たるものの、仮想キーボードが表示されないという困った現象が発生します。
それを解消を目指します。
検証環境
問題があるコード
import React, { useEffect, useRef, useState } from "react";
interface Item {
id : number
name : string
}
const App = () => {
const [items, setItems] = useState<Item[]>([])
useEffect(() => {
const element = document.getElementById("input-" + items.length)
if (element) {
element.focus()
}
}, [items])
const onClickButton = () => {
setItems(old => {
return [...old, {
id : old.length + 1,
name : "Hoge"
}]
})
}
return <>
{items.map(item => {
return <div key={item.id}>
<input id={"input-" + item.id} type="text"></input>
</div>
})}
<button onClick={onClickButton} type="button">追加</button>
</>
}
iOSで閲覧するとわかると思いますが、フォーカスは当たるもの仮想キーボードが表示されません。
作戦
ダミーのテキストフィールドに一瞬フォーカスを当てて、フォーカスを当てたい要素に改めてフォーカスを当てる。
ダミーテキストフィールドを上手く隠せたらよかったのですが、display:noneにするとそもそもフォーカスが取れなくなるので、そこは辛いところ。
width,heightを1pxにするなど上手く隠すか、最初から表示されている何か別のテキストフィールドをダミーテキストと使うと良いかと思います。
キーボードが表示されるコード
import React, { useEffect, useRef, useState } from "react";
interface Item {
id : number
name : string
}
const App = () => {
const [items, setItems] = useState<Item[]>([])
useEffect(() => {
const element = document.getElementById("input-" + items.length)
if (element) {
element.focus()
}
}, [items])
const onClickButton = () => {
setItems(old => {
if (refDummyInput.current) {
refDummyInput.current.focus()
}
return [...old, {
id : old.length + 1,
name : "Hoge"
}]
})
}
const refDummyInput = useRef<HTMLInputElement>(null)
return <>
{}
<div>
<input type="text" placeholder="ダミー" ref={refDummyInput}></input>
</div>
{}
{items.map(item => {
return <div key={item.id}>
<input id={"input-" + item.id} type="text"></input>
</div>
})}
<button onClick={onClickButton} type="button">追加</button>
</>
}