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

调用props传递的父函数时React useState更新失效问题

问题

我想在UI元素被选中时阻止重复点击,通过useState维护upArrowSelected和downArrowSelected两个布尔变量控制投票逻辑。但调用父组件的passVotes函数更新并排序列表后,这两个useState的状态更新逻辑就失效了——单独使用useState时逻辑正常,现在调用props.passVotes()后,updateDownArrowSelected()和updateUpArrowSelected()似乎没生效,我希望这两个操作都能执行。

子组件代码

const QuoteBubble = (props: Quote) =>{

    const [upArrowSelected, updateUpArrowSelected] = useState(false);
    const [downArrowSelected, updateDownArrowSelected] = useState(false);
    
    const upVote = (event: React.MouseEvent)=>{
        event.preventDefault();
        if(upArrowSelected){
            updateDownArrowSelected(false);
            updateUpArrowSelected(false);
            props.passVotes(props.votes - 1, props.quoteId);
            return;
        }
        updateDownArrowSelected(false);
        updateUpArrowSelected(true);
        props.passVotes(props.votes + 1, props.quoteId);
    }
    
    const downVote = (event: React.MouseEvent) =>{
        event.preventDefault();
        if(downArrowSelected){
            props.passVotes(props.votes + 1, props.quoteId);
            updateUpArrowSelected(false);
            updateDownArrowSelected(false);  
            return;
        }
        props.passVotes(props.votes - 1, props.quoteId);
        updateUpArrowSelected(false);
        updateDownArrowSelected(true);     
    }
    
    return(
        <Card variant="outlined" className='quote-bubble'>
            <div className='quote-text quote-box'>
                <a href='' className={`btn ${upArrowSelected ? 'arrow-selected' : 'arrow-not-selected'}`} 
                   onClick={upVote}>
                    <FontAwesomeIcon icon={faSquareCaretUp} className='up-arrow'/>
                </a>
                <h1>{props.quote}</h1>
            </div>
            <div className='quote-text author-box'>
                <a href='' className={`btn ${downArrowSelected ? 'arrow-selected' : 'arrow-not-selected'}`} 
                   onClick={downVote}>
                    <FontAwesomeIcon icon={faSquareCaretDown} className='down-arrow'/>
                </a>
                <Chip label={props.votes} variant="outlined" className='votes-pill' />
                <p>{props.author}</p>
            </div>
        </Card>
    )
}

父组件代码

const QuoteList = (props: QuoteListProps) =>{

    const sortQuotes = (quotes: QuoteDataInput[]) => {
        quotes.sort((quoteA: QuoteDataInput, quoteB: QuoteDataInput) =>{
            return quoteB.votes - quoteA.votes;
        });
        props.setQuotes({quotes: [...quotes]});
        return quotes;
    }

    const GetVotes = (votes: number, id: number) =>{
        const updatedQuote = props.quoteList.find((quote) => quote.quoteId === id)
        if(updatedQuote){
            updatedQuote.votes = votes;
            props.setQuotes({quotes:[...props.quoteList, updatedQuote]});
        }
        sortQuotes(props.quoteList)
    }

    return(
        <ul className='quote-list'>
            {props.quoteList.map((quote: QuoteDataInput)=>(
                <li><QuoteBubble quote={quote.quote} author={quote.author} key={Math.random()} votes={quote.votes} passVotes={GetVotes} quoteId={quote.quoteId}/></li>
            ))}
        </ul>
    )
}
解决方案

问题根源在两个核心错误,修复后即可恢复状态逻辑:

1. 替换不稳定的组件Key

你用Math.random()作为QuoteBubble的key,会导致父组件更新时所有子组件被强制销毁重建,之前的useState状态直接丢失,看起来像是状态更新失效。必须用稳定唯一的key,比如quote.quoteId:

{props.quoteList.map((quote: QuoteDataInput)=>(
    <li key={quote.quoteId}>
        <QuoteBubble 
            quote={quote.quote} 
            author={quote.author} 
            votes={quote.votes} 
            passVotes={GetVotes} 
            quoteId={quote.quoteId}
        />
    </li>
))}

2. 修复父组件的列表更新逻辑

父组件GetVotes里直接修改原数组元素,还错误地把更新后的元素追加到数组末尾,导致数组出现重复项,排序逻辑也基于错误数组执行。正确做法是创建新数组替换目标元素:

const GetVotes = (votes: number, id: number) =>{
    // 创建新数组,替换对应id的元素
    const updatedQuotes = props.quoteList.map(quote => 
        quote.quoteId === id ? {...quote, votes} : quote
    );
    sortQuotes(updatedQuotes);
}

// 同时修改sortQuotes,基于新数组排序后更新状态
const sortQuotes = (quotes: QuoteDataInput[]) => {
    // 先拷贝数组再排序,避免修改原数据
    const sortedQuotes = [...quotes].sort((quoteA, quoteB) => quoteB.votes - quoteA.votes);
    props.setQuotes({quotes: sortedQuotes});
}

可选优化:合并状态简化逻辑

可以把两个布尔状态合并成一个,用selectedVote表示当前选中的投票类型,让逻辑更简洁:

const [selectedVote, setSelectedVote] = useState<'up' | 'down' | null>(null);

const upVote = (event: React.MouseEvent)=>{
    event.preventDefault();
    let newVotes = props.votes;
    if(selectedVote === 'up'){
        newVotes -= 1;
        setSelectedVote(null);
    } else {
        // 如果之前选了down,需要抵消后再加1
        newVotes += selectedVote === 'down' ? 2 : 1;
        setSelectedVote('up');
    }
    props.passVotes(newVotes, props.quoteId);
}

const downVote = (event: React.MouseEvent) =>{
    event.preventDefault();
    let newVotes = props.votes;
    if(selectedVote === 'down'){
        newVotes += 1;
        setSelectedVote(null);
    } else {
        // 如果之前选了up,需要抵消后再减1
        newVotes -= selectedVote === 'up' ? 2 : 1;
        setSelectedVote('down');
    }
    props.passVotes(newVotes, props.quoteId);
}

对应的className也可以简化:

className={`btn ${selectedVote === 'up' ? 'arrow-selected' : 'arrow-not-selected'}`}
className={`btn ${selectedVote === 'down' ? 'arrow-selected' : 'arrow-not-selected'}`}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 05:35:19