React useState更新异常:需多次点击按钮才生效的问题排查
React useState状态更新异常:多次点击才生效的解决方案
你的问题核心出在状态不同步和闭包导致的旧值引用,还有一段完全多余的代码:
- 子组件
TopButton自己维护的buttonClicked状态,和父组件的categories没有绑定,当父组件分类状态变化时,子组件本地状态没同步更新,导致点击逻辑判断错误,必须点两次才能让状态对齐。 - 父组件
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
相关产品推荐
相关产品推荐

