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

Chakra UI Menu显示时搜索输入框频繁失焦问题排查

搜索输入框连续输入时失去焦点问题排查

我实现了一个带搜索结果展示的输入框,搜索过程中Input组件内会显示加载指示器,但连续输入多个字符、搜索结果更新时,输入框会失去焦点。使用的Chakra UI版本为@chakra-ui/react ^3.4.0。

相关代码

export const HomeSearch = () => {
  const { search, results, setResults } = useSearch();
  const [query, setQuery] = useState('');
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const searchFlies = debounce(async (searchQuery: string) => {
      if (!searchQuery.trim()) {
        setResults(undefined);
        return;
      }

      setIsLoading(true);
      try {
        await search(searchQuery);
      } catch (error) {
        console.error('Search error:', error);
        setResults(undefined);
      } finally {
        setIsLoading(false);
      }
    }, 300);
    searchFlies(query);
    return () => {
      searchFlies.cancel();
    };
  }, [query, search, setResults]);

  const handleClick = ({ id, type }: { id: string, type: string }) => {
    router.push(`${type}/${id}`);
  };

  const cleaned = sortedArrayAZ<SearchResultShape>(results || []);

  return (
    <Flex justify="center" marginTop={-8} zIndex={1}>
      <Box>
        <InputGroup
          endElement={
            isLoading ? (
              <Spinner color={'brand.primary'} />
            ) : (
              <IoIosCloseCircleOutline
                cursor={'pointer'}
                onClick={() => setQuery('')}
                size={32}
              />
            )
          }
          flex="1"
          startElement={<IoIosSearch size={32} />}
          zIndex={1000}
        >
          <Input
            autoFocus
            borderRadius={32}
            boxShadow="lg"
            id="homeSearch"
            onChange={(e) => setQuery(e.target.value)}
            p={8}
            placeholder={'Search by fly name or species'}
            ps={16}
            value={query}
            w={{ base: '90vw', md: '50vw' }}
          />
        </InputGroup>
        <Menu.Root open={!!cleaned?.length || (!!query.length && !isLoading)}>
          <Menu.Content
            marginTop={-8}
            maxH={'35vh'}
            overflow={'scroll'}
            paddingTop={12}
            position={'absolute'}
            w={{ base: '90vw', md: '50vw' }}
            zIndex={500}
          >
            <Skeleton loading={false}>
              {cleaned?.length ? (
                cleaned.map((result) => (
                  <Menu.Item
                    justifyContent={'space-between'}
                    key={result.id}
                    my={2}
                    onClick={() =>
                      handleClick({ id: result.id, type: result.type })
                    }
                    value={result.id}
                  >
                    <HStack>
                      <Avatar
                        colorPalette={'blue'}
                        name={result.name}
                        src={result.thumbnail}
                      />
                      <Text>{removeAZ(result.name)}</Text>
                    </HStack>
                    <HStack>
                      {result.metadata.map((meta) => (
                        <MetaBadge key={meta} text={meta} />
                      ))}
                    </HStack>
                  </Menu.Item>
                ))
              ) : (
                <Menu.Item
                  justifyContent={'space-between'}
                  my={2}
                  value={'uknown'}
                >
                  <Text>Unable to find results for {query}</Text>
                </Menu.Item>
              )}
            </Skeleton>
          </Menu.Content>
        </Menu.Root>
      </Box>
    </Flex>
  );
};

已尝试的无效方案

  • 将debounce移入useCallback,但ESLint报错:React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. eslintreact-hooks/exhaustive-deps
  • 添加ref并通过useEffect监听结果,调用focus()聚焦输入框

问题原因

1. InputGroup子元素切换导致DOM重建

当isLoading状态变化时,InputGroup的endElement会在<Spinner>和<IoIosCloseCircleOutline>之间完全替换。Chakra UI的InputGroup在子元素替换时,会触发整个输入容器的重渲染,输入框DOM节点被重建,自然失去焦点。

2. Menu.Root的open状态频繁变动引发干扰

Menu.Root的open状态依赖cleaned.length、query.length和isLoading三个变量:

  • 输入时query变化触发debounce,isLoading从false变为true,此时open状态会切换(因为!isLoading变为false)
  • 搜索完成后isLoading变回false,同时results更新导致cleaned.length变化,open状态再次切换
    这种频繁的菜单显示/隐藏会引发DOM重排,干扰输入框的焦点状态。

3. debounce函数重复创建的间接影响

每次query更新时,useEffect都会重新创建一个新的searchFlies debounce函数。虽然调用了cancel,但状态更新的时序可能导致不必要的组件重渲染,进一步加剧焦点丢失的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 04:35:53