底辺SE奮闘記

年収300万SEブログ

【React / iOS】useStateで増減するinput要素にフォーカスを当ててキーボードも表示させる

困りごと

ReactでuseState管理されているinput要素が増えた時に、その増えたinput要素にフォーカスを当てたい、というニーズは結構あるように思います。

Androidは比較的素直かと思いますが、(少なくともiOS16.6の)iOSSafariではフォーカスは当たるものの、仮想キーボードが表示されないという困った現象が発生します。

それを解消を目指します。

検証環境

問題があるコード

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>
    </>
}