简体   繁体   English

使用 setInterval 定期更新 append 新数据时,Recharts 图表不会更新

[英]Recharts chart not updating when using setInterval to periodically append new data

I'm trying to set up a web page that adds a score onto a line chart every 2 minutes and it doesn't seem to be working.我正在尝试设置一个 web 页面,该页面每 2 分钟将一个分数添加到折线图上,但它似乎不起作用。

The chart that I am using is the line chart from the Recharts, and the initial value of scoreData is an empty list and the initial form of {time} is {h:0, m:0, s:0}.我使用的图表是来自Recharts的折线图,scoreData的初始值是一个空列表,{time}的初始形式是{h:0, m:0, s:0}。

I am using context and hooks so that the page won't stop counting even if the user for some reason temporarily visits other pages that I will be creating.我正在使用上下文和挂钩,这样即使用户出于某种原因临时访问我将创建的其他页面,页面也不会停止计数。

The code that I wrote down is as follows;我写下的代码如下;

const Timer = () => {
  const {
    scoreData,
    setScoreData,
    time,
    setTime,
    interv,
    setInterv,
  } = useContext(BaseContext);

const addNewData = () => {
    setScoreData([...score, {Some New data}])
}

const start = () => {
    run();
    setInterv(setInterval(run, 1000));
 };

var updatedS = time.s,
    updatedM = time.m,
    updatedH = time.h;

  const run = () => {
    updatedS++;
    if (updatedS === 60) {
      updatedM++;
      updatedS = 0;
      if (updatedM % 2 == 0) {
        addNewData();
        console.log(scoreData.length);
      }
    }
    if (updatedM === 60) {
      updatedH++;
      updatedM = 0;
    }
    return setTime({
      s: updatedS,
      m: updatedM,
      h: updatedH,
    });
  };

return (
<div>
// The code of the line chart goes in here.

<button onClick={start}>Start</button>

<button onClick={addNewData}>Test Button</button>

</div>
)

According to the above code, a new data should be added to the list every two minutes, but the chart never showed any changes.根据上面的代码,每两分钟应该向列表中添加一个新数据,但图表从未显示任何变化。

When I confirmed the status of the scoreData by using console.log(scoreData.length), the console just showed 0, which means the data is not being appended into the list.当我使用 console.log(scoreData.length) 确认 scoreData 的状态时,控制台只显示 0,这意味着数据没有被附加到列表中。

So I tried a different approach by making a Test Button that manually adds a new data and it worked just fine;所以我尝试了一种不同的方法,通过制作一个手动添加新数据的测试按钮,它工作得很好; the list was filled with new data every time I pushed it.每次我推送时,列表中都充满了新数据。

In this case, what can I do to make the chart receive the data periodically in accordance to the time that I set up?这种情况下,如何让图表按照我设置的时间定时接收数据呢?

Ciao, unfortunately with react hooks you cannot use setInterval in the way you are using it.再见,不幸的是,对于反应挂钩,您不能以您使用它的方式使用setInterval The problem is related to hooks itself.问题与钩子本身有关。 According to your code example, you want to add a new data to your scoreData each second.根据您的代码示例,您希望每秒向scoreData添加一个新数据。 So you call useInterval that launches run that, if (updatedM % 2 == 0) , calls addNewData that finally adds value to your scoreData .因此,您调用useInterval启动runif (updatedM % 2 == 0)调用addNewData最终为您的scoreData添加值。

Unfortunately, with hooks you are not sure that scoreData is already updated with data you added in previuos setInterval .不幸的是,使用钩子你不确定scoreData是否已经更新为你在之前的setInterval中添加的数据。 Why?为什么? Because hooks are async!因为钩子是异步的!

How to solve this?如何解决这个问题? With another hook, in particular a custom hook!使用另一个挂钩,特别是定制挂钩!

Here a working example. 是一个工作示例。 Lets see my custom hook useInterval :让我们看看我的自定义钩子useInterval

function useInterval(callback, delay) {
    const savedCallback = useRef();

    // Remember the latest callback.
    useEffect(() => {
      savedCallback.current = callback;
    }, [callback]);

    // Set up the interval.
    useEffect(() => {
      function tick() {
        savedCallback.current();
      }
      if (delay !== null) {
        let id = setInterval(tick, delay);
        return () => clearInterval(id);
      }
    }, [delay]);
  }

As you can see inside my custom hook I use 2 useEffect : 1 to remember the latest useInterval callback (using useRef );正如您在我的自定义挂钩中看到的那样,我使用 2 useEffect : 1 来记住最新的useInterval回调(使用useRef ); and the other one is used to set the delay of the setInterval (this last useEffect returns a clearInterval to cancel the last setInterval ).另一个用于设置setInterval的延迟(最后一个useEffect返回一个clearInterval以取消最后一个setInterval )。

Then I can call useInterval inside my component:然后我可以在我的组件中调用useInterval

useInterval(() => {
if (sampling) {
  let result = _.cloneDeep(list1);
  result.push({
    x: Math.floor(Math.random() * 100),
    y: Math.floor(Math.random() * 100)
  });
  console.log(result);
  setlist1(result);
}
}, 1000);

list1 is my array of values (your scoreData ) and sampling is a bool state variable that I use to run/stop insertion of new vlaues into list1 . list1是我的值数组(你的scoreData ), sampling是一个 bool state 变量,我用它来运行/停止将新值插入list1

Finally, my return :最后,我的return

return (
    <div>
      <ScatterChart
        width={600}
        height={400}
        margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
      >
        <CartesianGrid />
        <XAxis type="number" dataKey={"x"} />
        <YAxis type="number" dataKey={"y"} />
        <Tooltip cursor={{ strokeDasharray: "3 3" }} />
        <Legend />
        <Scatter
          name="values"
          data={list1}
          fill="#8884d8"
          line
          shape="circle"
        />
      </ScatterChart>
      <button onClick={start}>
        {sampling ? "Stop sampling" : "Start sampling"}
      </button>
    </div>
  );

I used ScatterChart (graphically equal to LineChart ) and a button to start/stop data sampling.我使用了ScatterChart (在图形上等于LineChart )和一个按钮来开始/停止数据采样。

I assume that setScoreData([...score, {Some New data}]) should be setScoreData([...scoreData, {Some New data}])我假设setScoreData([...score, {Some New data}])应该是setScoreData([...scoreData, {Some New data}])

Your setInterval receives a closure with fixed scoreData and keeps calling it.您的setInterval收到一个带有固定scoreData的闭包并继续调用它。 So setScoreData([...scoreData, {Some New data}]) always sets the same data.所以setScoreData([...scoreData, {Some New data}])总是设置相同的数据。 In your case it is better to fix this issue with useRef hook:在您的情况下,最好使用useRef挂钩解决此问题:

  const {
    scoreData,
    setScoreData,
    time,
    setTime,
    interv,
    setInterv,
  } = useContext(BaseContext);
  const scoreDataRef = useRef(scoreData);
  scoreDataRef.current = scoreData;

  const addNewData = () => {
    setScoreData([...scoreDataRef.current, {Some New data}])
  }

Test button works because it gets a new closure on each render.测试按钮有效,因为它在每次渲染时都会得到一个新的闭包。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM