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

Next.js组件中useState引发动态样式延迟生效问题求助

问题描述

我正在开发一款Next.js应用,使用Chakra UI组件库。当前实现了多条件筛选功能,需求如下:

  • 无筛选条件时自动选中「All」按钮
  • 取消所有筛选时,按钮恢复初始样式
  • 支持多筛选条件同时生效

但目前按钮的动态样式更新总是滞后一步,样式依赖view状态,猜测是useState的异步特性导致,但不知道如何修复。相关代码如下:

const [view, setView] = useState<string[]>(["all"]);

const story = stories?.holidays;
const joke = stories?.jokes;
const idiom = stories?.idioms;
const factoid = stories?.culturalTips;

const handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    const target = e.currentTarget as HTMLButtonElement;
    const value = target.innerText.toLowerCase();

    setView((prevState) => {
      if (value === "all") {
        return ["all"];
      } else {
        // 如果选中「all」时点击其他筛选,取消「all」
        if (prevState.includes("all")) {
          return [value];
        } else {
          // 切换其他筛选的选中状态
          if (prevState.includes(value)) {
            const filteredView = prevState.filter(v => v !== value);
            // 无筛选时自动选中「all」
            return filteredView.length === 0 ? ["all"] : filteredView;
          } else {
            return [...prevState, value];
          }
        }
      }
    });
  };

return (
    <Flex>
      <Button
        justifyContent={'center'}
        backgroundColor={view.includes('all') ? 'gray.200' : 'orange.50'}
        textColor={view.includes('all') ? 'black' : 'white'}
        disabled={isLoading}
        onClick={(e) => handleClick(e)}
      >
        <Text fontSize={'sm'}>All</Text>
      </Button>
      <Button
        justifyContent={'center'}
        backgroundColor={view.includes('story') ? 'gray.200' : 'orange.50'}
        textColor={view.includes('story') ? 'black' : 'white'}
        disabled={isLoading}
        onClick={(e) => handleClick(e)}
      >
        <Text fontSize={'sm'}>Story</Text>
      </Button>
      {/* 其他按钮省略 */}
    </Flex>
    {/* 筛选结果渲染逻辑省略 */}
)

解决方案

问题根源并非useState的异步特性,而是依赖DOM元素的innerText获取筛选值存在不确定性(比如组件嵌套、文本空格、渲染差异等),导致状态更新逻辑接收到的value不符合预期,进而造成样式更新滞后。

修复步骤

  1. 直接传入明确的筛选值:给每个Button组件绑定点击事件时,直接传入对应的筛选标识(如'all'、'story'),避免从DOM读取文本。
  2. 保持状态更新逻辑的正确性:原有的函数式更新逻辑是正确的,只需替换value的来源。

修改后的代码

const [view, setView] = useState<string[]>(["all"]);

const story = stories?.holidays;
const joke = stories?.jokes;
const idiom = stories?.idioms;
const factoid = stories?.culturalTips;

// 调整handleClick,直接接收筛选值
const handleClick = (value: string) => {
    setView((prevState) => {
      if (value === "all") {
        return ["all"];
      } else {
        if (prevState.includes("all")) {
          return [value];
        } else {
          if (prevState.includes(value)) {
            const filteredView = prevState.filter(v => v !== value);
            return filteredView.length === 0 ? ["all"] : filteredView;
          } else {
            return [...prevState, value];
          }
        }
      }
    });
  };

return (
    <Flex>
      <Button
        justifyContent={'center'}
        backgroundColor={view.includes('all') ? 'gray.200' : 'orange.50'}
        textColor={view.includes('all') ? 'black' : 'white'}
        disabled={isLoading}
        onClick={() => handleClick('all')}
      >
        <Text fontSize={'sm'}>All</Text>
      </Button>
      <Button
        justifyContent={'center'}
        backgroundColor={view.includes('story') ? 'gray.200' : 'orange.50'}
        textColor={view.includes('story') ? 'black' : 'white'}
        disabled={isLoading}
        onClick={() => handleClick('story')}
      >
        <Text fontSize={'sm'}>Story</Text>
      </Button>
      {/* 其他按钮示例 */}
      <Button
        justifyContent={'center'}
        backgroundColor={view.includes('joke') ? 'gray.200' : 'orange.50'}
        textColor={view.includes('joke') ? 'black' : 'white'}
        disabled={isLoading}
        onClick={() => handleClick('joke')}
      >
        <Text fontSize={'sm'}>Joke</Text>
      </Button>
    </Flex>
    {/* 筛选结果渲染逻辑保持不变 */}
    {factoid &&
    story &&
    idiom &&
    joke &&
    (view.includes('all') || view.includes('story')) &&
    story.map((holidayValue, idx) => (
    <Box
      border={'1px solid #E2E2E2'}
      borderRadius={'10px'}
      padding={'20px'}
      my={'20px'}
      boxShadow="lg"
      key={idx}
    >
      <Image
        width={40}
        height={40}
        className="stories-icon"
        src={storyImg}
        alt="story"
      />
      <Text>{holidayValue}</Text>
    </Box>
    ))}
    {/* 其他类型结果渲染省略 */}
)

额外优化建议

可以将筛选按钮的配置抽成数组,通过循环渲染按钮,避免重复代码:

const filterButtons = [
  { label: 'All', value: 'all' },
  { label: 'Story', value: 'story' },
  { label: 'Joke', value: 'joke' },
  { label: 'Idiom', value: 'idiom' },
  { label: 'Factoid', value: 'factoid' },
];

// 渲染按钮
<Flex>
  {filterButtons.map((btn) => (
    <Button
      key={btn.value}
      justifyContent={'center'}
      backgroundColor={view.includes(btn.value) ? 'gray.200' : 'orange.50'}
      textColor={view.includes(btn.value) ? 'black' : 'white'}
      disabled={isLoading}
      onClick={() => handleClick(btn.value)}
    >
      <Text fontSize={'sm'}>{btn.label}</Text>
    </Button>
  ))}
</Flex>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 05:14:56