简体   繁体   中英

Why does updating state using React Hooks, MomentJS object and interval/timeout not trigger re-render?

I'm trying to make a countdown timer using React-Native, React Hooks, MomentJS and (setTimeout/setInterval). Whatever approach I try to use, it fails. The problem is that component is never re-rendered.

I tried to follow the official React Hooks documentation, a few articles on Medium, for example The Iceberg of React Hooks but nothing works.

One possibility is that it needs deep clone of the MomentJS object, but it's an inefficient approach I guess.

This is one of the reproducible examples that I've tried.

const Timer = () => {
  const [time, setTime] = useState(moment.duration(30, 'seconds'))
  const intervalRef = useRef()

  useEffect(() => {
    intervalRef.current = setTimeout(() => {
      setTime(prevTime => prevTime.subtract(1, 'second'))
    }, 1000)

    return () => {
      clearInterval(intervalRef.current)
      intervalRef.current = null
    }
  })

  return (
    <View>
      {time.asSeconds()}
    </View>
  )

One possibility is that it needs deep clone of the MomentJS object, but it's an inefficient approach I guess.

Yes exactly. React doesn't rerender if the current and the previous state equal. You could just store the seconds in the state.

And you don't need that ref.

const Timer = () => {
  const [time, setTime] = useState(30 /*s*/)

  useEffect(() => {
    const timeout = setTimeout(() => {
      setTime(prevTime => prevTime - 1);
    }, 1000)

    return () => clearTimeout(timeout);
  }, [time])

  return (
   <View>
     {time}
   </View>
 );

You're correct, it isn't re-rendering because your moment object is the same (but mutated) on every tick. You can easily get it working by adding .clone() in your setTime updater:

const Timer = () => {
  const [time, setTime] = useState(moment.duration(30, "seconds"));
  const intervalRef = useRef();

  useEffect(() => {
    intervalRef.current = setTimeout(() => {
      setTime(prevTime => prevTime.clone().subtract(1, 'second'))
    }, 1000)

    return () => {
      clearInterval(intervalRef.current)
      intervalRef.current = null
    }
  })

  return <div>{time.asSeconds()}</div>;
};

Working sandbox here: https://codesandbox.io/s/gifted-euler-e8xg5

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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