React.js中responseCount状态无法更新至最新值的问题求助
React状态更新问题:调用API后responseCount未同步更新
问题描述
调用submitPulseResponse接口获取响应后,responseCount状态值未更新。需求是实现一个计数器,投票后根据接口返回的最新total值更新responseCount并显示在UI上。
示例:当前计数器值为5,投票后应更新为接口返回的最新总数,但实际仍保持旧值。怀疑是React状态更新的异步特性导致,资深工程师建议用回调函数更新状态。
代码片段
import React, { useState, useEffect } from 'react'; import { useCookies } from 'react-cookie'; import Tooltip from '@mui/material/Tooltip'; import './PulseCheck.scss'; import { handleEventsForAnalytics } from '../../utils/helper'; import eventBus from '../../utils/eventBus'; import pulseIcon from '../../assets/images/icons/Medflix/pulse.svg'; import counterIcon from '../../assets/images/icons/Medflix/counter.svg'; import { submitPulseResponse } from '../../services/service'; const PulseCheck = ({ question, options = [], page, variant, data, eachData, pulseData, toolTipText, }) => { const [responseCount, setResponseCount] = useState(0); const [showGraph, setShowGraph] = useState(false); const [optionOnePercent, setOptionOnePercent] = useState(0); const [optionTwoPercent, setOptionTwoPercent] = useState(0); const [cookies] = useCookies(['UserData']); const [hasVoted, setHasVoted] = useState(false); const [responseSubmitted, setResponseSubmitted] = useState(false); const token = cookies?.UserData?.token; const objectId = cookies?.UserData?.ObjectID; const isUserLoggedIn = !!token && !!objectId; const currentNewsId = eachData?.fields?.Id?.value || eachData?.Id; const votedNewsIds = cookies.UserData?.VotedNewsIds?.split(',') || []; useEffect(() => { const hasUserVoted = votedNewsIds.includes(String(currentNewsId)); if (pulseData && Array.isArray(pulseData.data)) { const currentPulseData = pulseData.data.find((d) => d.newsId === String(currentNewsId)); if (currentPulseData) { if (hasUserVoted === true) { updateGraphPercentages(currentPulseData); setShowGraph(true); } setResponseCount(currentPulseData.total); } } }, [token, pulseData, currentNewsId, votedNewsIds, cookies.UserData?.VotedNewsIds]); useEffect(() => { console.log('responseCount has been updated to:', responseCount); }, [responseCount]); useEffect(() => { if (responseSubmitted && pulseData) { setShowGraph(true); setResponseSubmitted(false); } }, [responseSubmitted, pulseData]); const updateGraphPercentages = (pulseData) => { const optionOneCount = pulseData.optionOneCount; const optionTwoCount = pulseData.optionTwoCount; const total = pulseData.total; if (total > 0) { const calculatedOptionOnePercent = ((optionOneCount / total) * 100).toFixed(1); const calculatedOptionTwoPercent = ((optionTwoCount / total) * 100).toFixed(1); setOptionOnePercent(calculatedOptionOnePercent); setOptionTwoPercent(calculatedOptionTwoPercent); console.log('Total value: ', total); setResponseCount(total); if (calculatedOptionOnePercent === 100) { setOptionTwoPercent(null); } else if (calculatedOptionTwoPercent === 100) { setOptionOnePercent(null); } } }; const handleClick = (event, ev, item_location, type, title, url) => { event.stopPropagation(); let analyticsData = { type, title, url, }; handleEventsForAnalytics(event, ev, analyticsData, item_location); }; const handleResponse = async (event, option) => { event.preventDefault(); if (!isUserLoggedIn) { eventBus.dispatch('validateUser', { isLoggedIn: false, Msg: 'NotSignedIn' }); return; } if (hasVoted) { console.log('User has already voted'); return; } const newsId = eachData?.fields?.Id?.value || eachData?.Id || pulseData?.data?.newsId; if (!newsId) { console.error('No valid newsId to send'); return; } const selectedOption = option === options[0] ? 'OPTION_ONE' : 'OPTION_TWO'; const payload = { newsId: newsId, objectId: objectId, selectedOption: selectedOption, }; try { const response = await submitPulseResponse(payload, token); if (response.data.success) { const newPulseData = response.data.data; console.log(response.data.data.total); updateGraphPercentages(newPulseData); setHasVoted(true); setShowGraph(true); setResponseSubmitted(true); const updatedVotedNewsIds = [ ...(cookies.UserData?.VotedNewsIds?.split(',') || []), String(currentNewsId), ]; // cookies.set( // 'UserData', // { ...cookies.UserData, VotedNewsIds: updatedVotedNewsIds.join(',') }, // { path: '/' } // ); } } catch (error) { console.error('Error submitting pulse response:', error); } }; return ( <div className={`pulse-check ${hasVoted ? 'show-graph' : ''}`}> <div className="header"> <img src={pulseIcon} alt="Pulse Icon" className="pulse-icon" /> {question && <span className="title">Pulse Check</span>} <img src={counterIcon} alt="Counter Icon" className="counter-icon" /> <span className="response-count">{responseCount}</span> </div> <div className="content"> {question && <p className="question">{question}</p>} {isUserLoggedIn && showGraph === true && responseCount > 0 ? ( <div className="graph"> {options[0] && optionOnePercent > 0 && optionOnePercent !== null && ( <Tooltip title={toolTipText} arrow> <div className="graph-bar option-one" style={{ width: `${optionOnePercent}%` }}> {optionOnePercent}% </div> </Tooltip> )} {options[1] && optionTwoPercent > 0 && optionTwoPercent !== null && ( <Tooltip title={toolTipText} arrow> <div className="graph-bar option-two" style={{ width: `${optionTwoPercent + 1}%` }}> {optionTwoPercent}% </div> </Tooltip> )} </div> ) : ( <div className="buttons"> {options.map((option, index) => ( <button key={index} className={`response-button ${index === 0 ? 'first' : 'second'}`} onClick={(e) => handleResponse(e, option)} > {option} </button> ))} </div> )} </div> </div> ); }; export default PulseCheck;
问题根源
- 状态被旧数据覆盖:投票成功后调用
updateGraphPercentages更新了responseCount,但第一个useEffect依赖了votedNewsIds,而代码中更新CookieVotedNewsIds的逻辑被注释,导致votedNewsIds未更新,useEffect会用初始传入的pulseData(未包含最新投票数据)重新覆盖responseCount。 - 异步更新的潜在冲突:虽然接口直接返回最新
total,但如果存在基于旧状态更新的场景,未使用回调函数写法也可能导致状态不同步。
修复方案
1. 恢复Cookie更新逻辑
取消handleResponse中Cookie更新代码的注释,确保用户投票后VotedNewsIds同步更新,避免重复触发useEffect用旧数据覆盖状态:
const updatedVotedNewsIds = [ ...(cookies.UserData?.VotedNewsIds?.split(',') || []), String(currentNewsId), ]; cookies.set( 'UserData', { ...cookies.UserData, VotedNewsIds: updatedVotedNewsIds.join(',') }, { path: '/' } );
2. 调整useEffect逻辑,避免覆盖新状态
修改第一个useEffect,仅在用户未投票时用初始pulseData更新状态,已投票时保留接口返回的最新数据:
useEffect(() => { const hasUserVoted = votedNewsIds.includes(String(currentNewsId)); setHasVoted(hasUserVoted); if (pulseData && Array.isArray(pulseData.data)) { const currentPulseData = pulseData.data.find((d) => d.newsId === String(currentNewsId)); if (currentPulseData) { // 仅用户未投票时,用初始数据更新计数器 if (!hasUserVoted) { setResponseCount(currentPulseData.total); } if (hasUserVoted) { updateGraphPercentages(currentPulseData); setShowGraph(true); } } } }, [token, pulseData, currentNewsId, votedNewsIds]);
3. 状态更新使用回调函数(可选,针对异步场景)
如果需要基于之前的状态值更新,改用回调函数写法确保拿到最新状态:
// 在updateGraphPercentages中替换setResponseCount setResponseCount(prevCount => total);
验证要点
- 投票后检查控制台是否打印了接口返回的最新
total值 - 确认
responseCount的useEffect触发了更新日志 - 查看Cookie中
VotedNewsIds已包含当前新闻ID,避免重复投票
内容的提问来源于stack exchange,提问作者Dminiac
相关产品推荐
相关产品推荐

