React Hooks, setTimeout in useEffect not triggering until end, because of state updates

自古美人都是妖i 提交于 2021-02-11 14:21:55

问题


Context:

  • New messages are added (e.g. every two seconds, using setInterval).
  • Messages have status, either old or new. Newly added messages have a 'new' flag.
  • After every 5 seconds, all 'new' messages are designated 'old'. (setTimeout)

Problem:

  • Timeout is not triggering until the end. New messages are added, but they remain 'new' until all messages are added.
  • I suspect that after every update the timeout is being reset/cleared and because the updates are occurring faster than the timeout, then the timeout callback never triggers in time (as a result only the final timeout get's triggered).

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

const defaultMessages = [
  {
    message: "message 1",
    new: false
  },
  {
    message: "message 2",
    new: false
  },
  {
    message: "message 3",
    new: true
  }
];

export default function App() {
  const [messages, setMessages] = React.useState(defaultMessages);
  const messagesRef = React.useRef(messages);

  messagesRef.current = messages;

  // add a new message every 2 seconds
  useInterval(() => {
    messages.length < 10 &&
      setMessages([
        ...messages,
        { message: `message ${messages.length + 1}`, new: true }
      ]);
  }, 2000);

  React.useEffect(() => {
    const timer = setTimeout(() => {
      // console.log("change all messages from new to old");
      const updateMessages = messagesRef.current.map(m => ({
        ...m,
        new: false
      }));
      setMessages([...updateMessages]);
    }, 5000); // if you change this to duration less than 2 seconds then it runs just fine
    return () => clearTimeout(timer); // removing the timer, calls it with every message update and seemingly ignores the timeout duration
  });

  return (
    <div className="App">
      {messages.map(m => (
        <div key={m.message}>
          {m.message}, status: {m.new ? "new" : "old"}
        </div>
      ))}
    </div>
  );
}

Example code: https://codesandbox.io/s/settimeout-resetting-with-updates-ufl3b

Not sure how to approach this with React Hooks api. The timeouts need to persist, five seconds after every update. Where as right now every timeout seemed to be canceled or queued by the one that comes after it. I'm puzzled.

Thank you!


回答1:


Well, the main problem that I noticed is clearing the timeout on next render, meaning, if you render faster enough, you actually canceling the timeout callback instead of running it.

 React.useEffect(() => {
    const timer = setTimeout(() => {});
    // will clear the timeout on ***next*** render!
    return () => clearTimeout(timer);
  });

So after fixing it, and using functional updates instead of reference, seems like this code works:

export default function App() {
  const [messages, setMessages] = React.useState(defaultMessages);

  // add a new message every 2 seconds
  useInterval(() => {
    messages.length < 10 &&
      setMessages(prev => [
        ...prev,
        { message: `message ${messages.length + 1}`, new: true }
      ]);
  }, 2000);

  React.useEffect(() => {
    console.log("rendered");
    setTimeout(() => {
      setMessages(prev =>
        prev.map(m => ({
          ...m,
          new: false
        }))
      );
    }, 3000);
  });

  return (
    <div className="App">
      {messages.map(m => (
        <div key={m.message}>
          {m.message}, status: {m.new ? "new" : "old"}
        </div>
      ))}
    </div>
  );
}



来源:https://stackoverflow.com/questions/62641564/react-hooks-settimeout-in-useeffect-not-triggering-until-end-because-of-state

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!