Search code examples
javascriptreactjsuse-effect

react useEffect how to update data if two different dependency arrays updates at the same time


So I have two different useEffect and dependency arrays are different.

 const [dateFilterSort, setDateFilterSort] = useState({
    queryText: initialQueryText(params.sortName),
    cardText: initialCardText(params.sortName),
    start: params.startDate
      ? moment(params.startDate).startOf('day').toDate()
      : undefined,
    end: params.endDate
      ? moment(params.endDate).endOf('day').toDate()
      : undefined,
  });

 useEffect(() => {
    if (params.endDate) {
      setDateFilterSort({
        ...dateFilterSort,
        end: moment(params.endDate).endOf('day').toDate(),
      });
    }
  }, [params.endDate]);

  useEffect(() => {
    if (params.startDate) {
      setDateFilterSort({
        ...dateFilterSort,
        start: moment(params.startDate).startOf('day').toDate(),
      });
    }
  }, [params.startDate]);

The problem here is params.endDate and params.startDate get updated at the same time. So dateFilterSort.start gets updated correctly but since dateFilterSort.end is above, dateFilterSort.end was overwrited with second useEffect.

Is there any way that I can solve this?

I thought I could put two useEffect like below.

    useEffect(() => {
      if (params.startDate) {
        setDateFilterSort({
          ...dateFilterSort,
          start: moment(params.startDate).startOf('day').toDate(),
        });
      }
      if (params.endDate) {
        setDateFilterSort({
          ...dateFilterSort,
          end: moment(params.endDate).endOf('day').toDate(),
        });
      }
    }, [params.startDate, params.endDate]);

which caused infinite loop...

Also, just to see if I could update params.end, I use setTimeout but caused infinite loop again....

 useEffect(() => {
    if (params.endDate) {
      setTimeout(() => {
        setDateFilterSort({
          ...dateFilterSort,
          end: moment(params.endDate).endOf('day').toDate(),
        });
      }, 500);
    }
  }, [params.endDate]);

  useEffect(() => {
    if (params.startDate) {
      setDateFilterSort({
        ...dateFilterSort,
        start: moment(params.startDate).startOf('day').toDate(),
      });
    }
  }, [params.startDate]);

Solution

  • I had a similar problem recently. In my case the second setter was overwriting the first one.

    A good solution I came up with was to first do the necessary changes locally, and just then set the state only once:

    useEffect(() => {
      // Choose your favorite copy technique,
      // I'll go with spread operator for simplicity
      const dateFilterSortCopy = { ...dateFilterSort }
      
      if (params.startDate) {
        dateFilterSortCopy.startDate = moment(params.startDate).startOf('day').toDate()
      }
      
      if (params.endDate) {
        dateFilterSortCopy.endDate = moment(params.startDate).startOf('day').toDate()
      }
    
      setDateFilterSort(dateFilterSortCopy);
    }, [params.startDate, params.endDate]);
    

    This also saves on unnecessary re-renders.