skip to Main Content

I have a component that works as a time picker. Also I use MUI tabs to allow user choose between the whole day range and a range that user inputs. So user can enter desired time like from 14:00 to 20:00 and if they click Whole Day tab time picker interval becomes 00:00 and 23:59 and inputs become disabled.

To listen for chosen tab I have come up with a useEffect which works fine until I add all the dependencies. React Hook useEffect has missing dependencies: 'formTimeValues' and 'savedTime'. Either include them or remove the dependency array

    useEffect(() => {
    if (wholeDay) {
      setSavedTime(formTimeValues);
      setValue("defaultDeliveryWindow", wholeDayRange.defaultDeliveryWindow);
    } else {
      setValue("defaultDeliveryWindow", savedTime);
    }
  }, [setValue, wholeDay]);

But if I add all of the missing dependencies my time becomes rewritten every time and the functionality breaks. How can I refactor the useEffect or do something else to keep the functionality?

const wholeDayRange = {
  defaultDeliveryWindow: {
    defaultEarliestLoadTime: "00:00",
    defaultLatestLoadTime: "23:59",
  },
};

const TimePickerOwn = ({ wholeDay = false }) => {
  const { t } = useTranslation();
  const { getValues, watch, setValue } = useFormContext();

  const formTimeValues = getValues()?.defaultDeliveryWindow;

  const currentOption = useSelector((state) => state.orderIntake.currentOption);

  const { defaultDeliveryWindow } = currentOption;

  const defaultEarliestLoadTime =
    formTimeValues?.defaultEarliestLoadTime ||
    defaultDeliveryWindow?.defaultEarliestLoadTime;
  const defaultLatestLoadTime =
    formTimeValues?.defaultLatestLoadTime ||
    defaultDeliveryWindow?.defaultLatestLoadTime;

  const watchEarliest = watch(
    "defaultDeliveryWindow.defaultEarliestLoadTime",
    defaultEarliestLoadTime
  );
  const watchLatest = watch(
    "defaultDeliveryWindow.defaultLatestLoadTime",
    defaultLatestLoadTime
  );

  const [savedTime, setSavedTime] = useState({
    defaultEarliestLoadTime,
    defaultLatestLoadTime,
  });

  const [earliestIndex, setEarliestIndex] = useState(
    findCurrentIndex(watchEarliest)
  );

  const [latestIndex, setLatestIndex] = useState(findCurrentIndex(watchLatest));

  useEffect(() => {
    if (wholeDay) {
      setSavedTime(formTimeValues);
      setValue("defaultDeliveryWindow", wholeDayRange.defaultDeliveryWindow);
    } else {
      setValue("defaultDeliveryWindow", savedTime);
    }
  }, [setValue, wholeDay]);

  useEffect(() => {
    const timeOptionsLastIndex = timeOptions.length - 1;
    if (earliestIndex === latestIndex) {
      if (latestIndex === timeOptionsLastIndex) {
        setEarliestIndex(timeOptionsLastIndex - 1);
      } else {
        setLatestIndex((prev) => prev + 1);
      }
    } else if (earliestIndex > latestIndex) {
      setLatestIndex(earliestIndex + 1);
    } else if (latestIndex === 0) {
      setLatestIndex(earliestIndex + 1);
    }
  }, [earliestIndex, latestIndex]);

  useEffect(() => {
    const subscription = watch(({ defaultDeliveryWindow }, { type }) => {
      console.log(defaultDeliveryWindow);

      if (type) return;
      setEarliestIndex(
        findCurrentIndex(defaultDeliveryWindow.defaultEarliestLoadTime)
      );
      setLatestIndex(
        findCurrentIndex(defaultDeliveryWindow.defaultLatestLoadTime)
      );
    });
    return () => subscription.unsubscribe();
  }, [watch]);

  return (
    <>
      <TimeInput
        label={t("orderIntake.earliest")}
        id="startTime"
        time={watchEarliest}
        index={earliestIndex}
        setIndex={setEarliestIndex}
        options={timeOptions.slice(0, -1)}
        registerName="defaultEarliestLoadTime"
        isDisabled={wholeDay}
      />
      <Box marginBottom={2} />
      <TimeInput
        label={t("orderIntake.latest")}
        id="endTime"
        time={watchLatest}
        index={latestIndex}
        setIndex={setLatestIndex}
        options={timeOptions.slice(earliestIndex + 1)}
        registerName="defaultLatestLoadTime"
        isDisabled={wholeDay}
      />
    </>
  );
};

Here are the tabs:

const OITimeTabs = () => {
  const { t } = useTranslation();

  const [tabsValue, setTabsValue] = useState(0);
  const handleChange = (_, newValue) => {
    setTabsValue(newValue);
  };

  return (
    <>
      <Box sx={{ width: "100%", my: 2 }}>
        <StyledTabs
          onChange={handleChange}
          value={tabsValue}
          aria-label="Tabs to choose between whole day and time range delivery"
          TabIndicatorProps={{ hidden: true }}
        >
          <StyledTab
            label={t("orderIntake.timePicker.timeRange")}
            {...a11yProps(0)}
          />
          <StyledTab label={t("orderIntake.wholeDay")} {...a11yProps(1)} />
        </StyledTabs>
        <TimePickerOwn wholeDay={!!tabsValue} />
      </Box>
      <Typography sx={{ fontSize: "14px", color: "#00374d", mb: 1 }}>
        {`${t("orderIntake.timePicker.note")}: ${t(
          "orderIntake.timePicker.noteDescription",
          {
            from: "01:00 PM",
            to: "11:59 PM",
          }
        )}`}
      </Typography>
    </>
  );
};

2

Answers


  1. Chosen as BEST ANSWER

    Did it like this. Seems to be working. Though I don't like repetetive declaration of formTimeValues and calling setValue from inside setSavedTime seems odd too.

     useEffect(() => {
        const formTimeValues = getValues()?.defaultDeliveryWindow;
        if (wholeDay) {
          setSavedTime(formTimeValues);
          setValue("defaultDeliveryWindow", wholeDayRange.defaultDeliveryWindow);
        } else {
          setSavedTime((prev) => {
            setValue("defaultDeliveryWindow", prev);
          });
        }
      }, [setValue, wholeDay, getValues]);
    

  2. Maybe try to call setValue only when time has changed.

    // helper function
    const hasTimeChanged = (savedTime, formTimeValues) => {
      return (
        savedTime.defaultEarliestLoadTime !== formTimeValues?.defaultEarliestLoadTime ||
        savedTime.defaultLatestLoadTime !== formTimeValues?.defaultLatestLoadTime
      );
    };
    
    useEffect(() => {
      if (wholeDay) {
        setSavedTime(formTimeValues);
        setValue("defaultDeliveryWindow", wholeDayRange.defaultDeliveryWindow);
      } else if (hasTimeChanged(savedTime, formTimeValues)) {
        setValue("defaultDeliveryWindow", savedTime);
      }
    }, [setValue, wholeDay, savedTime, formTimeValues]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search