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

React useState更新异常:需多次点击按钮才生效的问题排查

React useState状态更新异常:多次点击才生效的解决方案

你的问题核心出在状态不同步和闭包导致的旧值引用,还有一段完全多余的代码:

  1. 子组件TopButton自己维护的buttonClicked状态,和父组件的categories没有绑定,当父组件分类状态变化时,子组件本地状态没同步更新,导致点击逻辑判断错误,必须点两次才能让状态对齐。
  2. 父组件Works里的这段useEffect完全无用,甚至可能引发不必要的重渲染:
useEffect(() => {
    const newArray = categories
    setCategories(newArray)
}, [categories])

它监听categories变化后又把同一个数组设置回去,除了浪费性能没有任何作用,直接删掉即可。
3. TopButton的handleClick里直接使用props.categories,会因为闭包问题拿到旧的状态值,导致更新逻辑出错。


修复方案

1. 移除父组件无效代码

删掉Works.tsx中那段监听categories的useEffect。

2. 子组件去掉本地状态,统一依赖父组件状态

子组件不需要自己维护buttonClicked,直接用父组件传过来的isHighLighted判断按钮选中状态,让父组件的categories成为唯一状态源,从根源避免状态不同步。

3. 使用函数式更新确保拿到最新状态

调用setCategories时,采用函数式更新(接收当前最新状态作为参数),避免闭包导致的旧值问题,确保每次更新都基于最新的分类数组。


修正后的代码

Works.tsx

import React, { useState } from 'react'
import './styles.css'
import { BsChevronUp } from 'react-icons/bs'
import TopButton from './TopButton/TopButton'

const Works = () => {
  const [Buttons, setButtons] = useState(false)
  const [categories, setCategories] = useState<string[]>([])

  const intersection = works.filter(work => categories.includes(work.type));

  return (
    <div className='works-container'>
      <div className='works-top-container'> Our Work </div>

      <div className='works-top-buttons-container'>
        {Buttons ? (
          <div className='everything-buttons-full'>
            <div>Show Me : </div>
            {buttonChoices.map((button, idx) => {
              return (
                <TopButton
                  key={idx}
                  isHighLighted={categories.includes(button.type)}
                  setCategories={setCategories}
                  buttonName={button.type}
                />
              )
            })}
            <div
              style={{ cursor: 'pointer' }}
              onClick={() => setButtons(false)}>
              <BsChevronUp size={18} />
            </div>
          </div>
        ) : (
          <div className='everything-buttons'>
            <div> Work With Us </div>
            <div className='show-me'>
              {' '}Show Me :{' '}
              <div
                className='everything-choice'
                onClick={() => setButtons(true)}>
                "Everything"
              </div>
            </div>
          </div>
        )}
      </div>

      <div className='our-works-container'>
        {categories.includes('Everything') ? (
          works.map((item, idx) => {
            return (
              <img key={idx} style={{ height:"100px", width:"100px"}} src={item.image} />
            )
          })
        ) : (
          intersection.map((item, idx) => {
            return (
              <img key={idx} style={{ height:"100px", width:"100px"}} src={item.image} />
            )
          })
        )}
      </div>
    </div>
  )
}

export default Works

TopButton.tsx

import React from 'react'
import './styles.css'

interface ITopButtonProps {
  buttonName: string
  setCategories: React.Dispatch<React.SetStateAction<string[]>>
  isHighLighted: boolean
}

const TopButton = (props: ITopButtonProps) => {
  const { buttonName, setCategories, isHighLighted } = props

  const handleClick = (categoryType: string) => {
    // 使用函数式更新,确保拿到最新的categories状态
    setCategories(prevCategories => {
      if (isHighLighted) {
        // 取消选中:过滤掉当前分类
        return prevCategories.filter(category => category !== categoryType)
      } else {
        if (categoryType !== 'Everything') {
          // 选中普通分类:先移除Everything,再添加当前分类
          return [...prevCategories.filter(category => category !== 'Everything'), categoryType]
        } else {
          // 选中Everything:直接重置为只包含Everything的数组
          return [categoryType]
        }
      }
    })
  }

  return (
    <div
      onClick={() => handleClick(buttonName)}
      className={isHighLighted ? 'buttons-clicked' : 'buttons'}
    >
      {buttonName}
    </div>
  )
}

export default TopButton

总结

  • 状态尽量保持单一数据源:子组件不要维护和父组件重复的状态,直接复用父组件传递的状态,避免不同步问题。
  • 当更新状态依赖于当前状态时,必须使用函数式更新(setState(prevState => newState)),确保拿到最新的状态值,规避闭包陷阱。
  • 避免编写无意义的useEffect,不仅浪费性能,还可能引发意想不到的状态异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 08:37:04