You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 15(TypeScript)中如何为Combobox的onChange指定类型?

解决Next.js 15(TypeScript)中Headless UI Combobox onChange的类型不匹配问题

错误原因

Headless UI的Combobox组件onChange回调可能会传入null(比如用户清空选择场景),但你通过useState生成的setSelectedPerson只接受{ id: number; name: string }类型或(prev) => { id: number; name: string }函数,不兼容null类型,因此触发类型报错。

解决方案

方案1:修改状态类型,允许接收null

显式声明状态为Person | null类型,同时在组件中处理空值场景:

import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions } from '@headlessui/react'
import { useState } from 'react'

// 定义Person类型
type Person = { id: number; name: string }

const people: Person[] = [
  { id: 1, name: 'Durward Reynolds' },
  { id: 2, name: 'Kenton Towne' },
  { id: 3, name: 'Therese Wunsch' },
  { id: 4, name: 'Benedict Kessler' },
  { id: 5, name: 'Katelyn Rohan' },
]

function Example() {
  // 声明状态允许为null
  const [selectedPerson, setSelectedPerson] = useState<Person | null>(people[0])
  const [query, setQuery] = useState('')

  const filteredPeople =
    query === ''
      ? people
      : people.filter((person) => {
          return person.name.toLowerCase().includes(query.toLowerCase())
        })

  return (
    <Combobox value={selectedPerson} onChange={setSelectedPerson} onClose={() => setQuery('')}>
      <ComboboxInput
        aria-label="Assignee"
        // 处理null场景,返回默认文本
        displayValue={(person) => person?.name ?? '未选择'}
        onChange={(event) => setQuery(event.target.value)}
      />
      <ComboboxOptions anchor="bottom" className="border empty:invisible">
        {filteredPeople.map((person) => (
          <ComboboxOption key={person.id} value={person} className="data-[focus]:bg-blue-100">
            {person.name}
          </ComboboxOption>
        ))}
      </ComboboxOptions>
    </Combobox>
  )
}

方案2:包装onChange回调,过滤null值

如果不想让状态接收null,可以包装一个回调函数,仅在传入值非空时更新状态:

import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions } from '@headlessui/react'
import { useState } from 'react'

const people = [
  { id: 1, name: 'Durward Reynolds' },
  { id: 2, name: 'Kenton Towne' },
  { id: 3, name: 'Therese Wunsch' },
  { id: 4, name: 'Benedict Kessler' },
  { id: 5, name: 'Katelyn Rohan' },
]

// 提取类型
type Person = typeof people[number]

function Example() {
  const [selectedPerson, setSelectedPerson] = useState(people[0])
  const [query, setQuery] = useState('')

  // 包装回调,过滤null
  const handleComboboxChange = (value: Person | null) => {
    if (value !== null) {
      setSelectedPerson(value)
    }
  }

  const filteredPeople =
    query === ''
      ? people
      : people.filter((person) => {
          return person.name.toLowerCase().includes(query.toLowerCase())
        })

  return (
    // 使用包装后的回调
    <Combobox value={selectedPerson} onChange={handleComboboxChange} onClose={() => setQuery('')}>
      <ComboboxInput
        aria-label="Assignee"
        displayValue={(person) => person?.name}
        onChange={(event) => setQuery(event.target.value)}
      />
      <ComboboxOptions anchor="bottom" className="border empty:invisible">
        {filteredPeople.map((person) => (
          <ComboboxOption key={person.id} value={person} className="data-[focus]:bg-blue-100">
            {person.name}
          </ComboboxOption>
        ))}
      </ComboboxOptions>
    </Combobox>
  )
}

内容的提问来源于stack exchange,提问作者Kinjo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:36:10