I'm trying to select all the text in the underlying input control after selecting an item with the HeadlessUI combobox. Following the example over at HeadlessUI I've done the following modifications:
import { Fragment, useState, useRef } from 'react'
import { Combobox, Transition } from '@headlessui/react'
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'
const people = [
{ id: 1, name: 'Wade Cooper' },
{ id: 2, name: 'Arlene Mccoy' },
{ id: 3, name: 'Devon Webb' },
{ id: 4, name: 'Tom Cook' },
{ id: 5, name: 'Tanya Fox' },
{ id: 6, name: 'Hellen Schmidt' },
]
export default function Example() {
const [selected, setSelected] = useState(people[0])
const [query, setQuery] = useState('')
const inputRef = useRef(null);
const filteredPeople =
query === ''
? people
: people.filter((person) =>
person.name
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''))
)
return (
<div className="fixed top-16 w-72">
<Combobox value={selected} onChange={(value) => {
setSelected(value);
if (inputRef.current) {
// Why isn't this selecting the text in the input?
inputRef.current.select();
}
}}>
<div className="relative mt-1">
<div className="relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm">
<Combobox.Input
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
displayValue={(person) => person.name}
onChange={(event) => setQuery(event.target.value)}
ref={inputRef}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</Combobox.Button>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
afterLeave={() => setQuery('')}
>
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{filteredPeople.length === 0 && query !== '' ? (
<div className="relative cursor-default select-none py-2 px-4 text-gray-700">
Nothing found.
</div>
) : (
filteredPeople.map((person) => (
<Combobox.Option
key={person.id}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 ${
active ? 'bg-teal-600 text-white' : 'text-gray-900'
}`
}
value={person}
>
{({ selected, active }) => (
<>
<span
className={`block truncate ${
selected ? 'font-medium' : 'font-normal'
}`}
>
{person.name}
</span>
{selected ? (
<span
className={`absolute inset-y-0 left-0 flex items-center pl-3 ${
active ? 'text-white' : 'text-teal-600'
}`}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Transition>
</div>
</Combobox>
</div>
)
}
Any ideas of how to make this work?
Try wrapping the setter inside your onChange handler with flushSync
:
import { flushSync } from "react-dom";
.
.
.
<Combobox value={selected} onChange={(value) => {
flushSync(() => {
setSelected(value);
})
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.select();
}
}}>