javascript - Why does updating state using React Hooks, MomentJS object and intervaltimeout not trigger re-render? - Stack Overf

I'm trying to make a countdown timer using React-Native, React Hooks, MomentJS and (setTimeoutset

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 ponent 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>
  )

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 ponent 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>
  )
Share Improve this question asked Aug 30, 2019 at 14:48 Honza SedloňHonza Sedloň 3741 gold badge11 silver badges28 bronze badges 4
  • Mutating the object isn't what you should with set*() (ad absurdum - you do not even need to call setTime() in that case). To avoid re-rendering when state/properties do not change is one of those basic optimization that makes React easy to use. To create a new duration object is a negligible overhead and you can quickly do it using clone(). Alternatively you might consider to use number instead of moment.Duration and to create a new object when rendering. Also note that you do not need intervalRef, you can save const ref = setTimeout(...); return () => clearInterval(ref); – Adriano Repetti Commented Aug 30, 2019 at 15:06
  • It's odd. I always thought that in order to not-rerender a ponent when the state did changed but the object was identical you had to implement it as a pure ponent – apokryfos Commented Aug 30, 2019 at 15:33
  • the state hasn't actually changed though, the reference to the object is the same – Will Jenkins Commented Aug 30, 2019 at 15:45
  • @WillJenkins in non-functional ponents calling setState always rerenders. Thats why I was confused, though now that I think about it set state probably always clones the state – apokryfos Commented Aug 30, 2019 at 15:49
Add a ment  | 

2 Answers 2

Reset to default 6

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

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>
 );

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745351888a4623882.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信