How to avoid infinity loop when using useEffect() hooks when added dependencies?

Viewed 426

I'm making a TimePicker like this

enter image description here

, I have another state is duration (number of hours from start to end), I made auto change end time when start or duration change function using useEffect().

It's work when I try to change the duration

but I'm got bug when I change start or end time here is my code:

    useEffect(() => {
        setEnd(moment(start).add(duration, 'hours'));
    }, [start, duration]);
    useEffect(() => {
        setDuration(moment.duration(end.diff(start)).asHours());
    }, [end, start]);

CodeSanBox Sample (it's difference with this question a bit, because I improved some logic about moment)

Update: Thanks for all your help, I did it, this is final code: CodeSanBox Final

2 Answers

You're problem is that you're modifying the thing that your useEffect hook depends on. If your hook is only supposed to run when time changes, then don't change time in the hook or it will loop endlessly.

Standard workaround if you do need a feedback loop but don't want it to be infinite have a boolean state that tells you whether or not to call timeChange inside your effect hook.

function Foo() {
  const [shouldUpdate, setShouldUpdate] = useState(false);
  const [time, setTime] = useState(...);

  useEffect(() => {
    if (shouldUpdate) {
      setShouldUpdate(false);
      setTime(...);
    }
  }, [shouldUpdate, time]);
  return whatever;
}

Then setShouldUpdate(true) should be called by whatever actually triggers the state change.

https://codesandbox.io/s/timepicker-8mo3x?file=/src/App.js

Ok I think I got it; can likely be refactored even further, though it works:

import React, { useState, useEffect } from "react";
import "./styles.css";
import { TimePicker } from "antd";
import "antd/dist/antd.css";
import moment from "moment";

const HHmm = "HH:mm";

export default function App() {
  const [time,setTime] = useState({
    start: "",
    end: "",
    duration: ""
  })
 
  const onStartChange = value => {
    if(time.start && time.end && time.duration) {
      const diffHours = value.diff(time.end, "hours");
      const diffMinutes = moment
        .utc(moment(value, HHmm).diff(moment(time.end, HHmm)))
        .format("mm");

      setTime((time)=>({
        start: value,
        end: time.end,
        duration: moment(`${diffHours} : ${diffMinutes}`, HHmm)
      }));
    } else {
      setTime((time)=>({
        start: value,
        end: time.end,
        duration: time.duration
      }))
    }

  };

  const onDurationChange = value => {
    if(time.start && time.end && time.duration) {
      const endToStartTime = moment(time.start)
          .add(value.hours(), "h")
          .add(value.minutes(), "m");

      setTime((time)=>({
        start: time.start,
        end: endToStartTime,
        duration: value
      }));
    } else {
      setTime((time)=>({
        start: time.start,
        end: time.end,
        duration: value
      }))
    }
    
  };

  const onEndChange = value => {
    if(time.start && time.end && time.duration) {
      const diffHours = value.diff(time.start, "hours");
      const diffMinutes = moment
        .utc(moment(value, HHmm).diff(moment(time.start, HHmm)))
        .format("mm");

      setTime((time)=>({
        start: time.start,
        end: value,
        duration: moment(`${diffHours} : ${diffMinutes}`, HHmm)
      }));
    } else {
      setTime((time)=>({
        start: time.start,
        end: value,
        duration: time.duration
      }))
    }
  };

  useEffect(()=>{
      if(time.start && time.end && !time.duration) {
        const diffHours = time.end.diff(time.start, "hours");
        const diffMinutes = moment
          .utc(moment(time.end, HHmm).diff(moment(time.start, HHmm)))
          .format("mm");
        setTime((time)=>({
          start: time.start,
          end: time.end,
          duration: moment(`${diffHours} : ${diffMinutes}`, HHmm)
        }));
      }

      if(time.start && !time.end && time.duration) {
        const endToStartTime = moment(time.start)
          .add(time.duration.hours(), "h")
          .add(time.duration.minutes(), "m");
        setTime((time)=>({
          start: time.start,
          end: endToStartTime,
          duration: time.duration
        }));
      }

      if(!time.start && time.end && time.duration) {
        const startToEndTime= moment(time.end)
          .subtract(time.duration.hours(), "h")
          .subtract(time.duration.minutes(), "m");
        setTime((time)=>({
          start: startToEndTime,
          end: time.end,
          duration: time.duration
        }));
      }
  },[time])

  return (
    <div className="App">
      <span>Start: </span>
      <TimePicker value={time.start} onChange={onStartChange} format={HHmm} />
      <span>Duration: </span>
      <TimePicker value={time.duration} onChange={onDurationChange} format={HHmm} />
      <span>End: </span>
      <TimePicker value={time.end} onChange={onEndChange} format={HHmm} />
    </div>
  );
}


Related