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

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;

问题根源

  1. 状态被旧数据覆盖:投票成功后调用updateGraphPercentages更新了responseCount,但第一个useEffect依赖了votedNewsIds,而代码中更新CookieVotedNewsIds的逻辑被注释,导致votedNewsIds未更新,useEffect会用初始传入的pulseData(未包含最新投票数据)重新覆盖responseCount。
  2. 异步更新的潜在冲突:虽然接口直接返回最新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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 12:42:05