Issue with React hook state while working with lodash debounce

Viewed 214

I'm not really sure what exactly is causing the issue. So I have a component that shows the amount of pictures according to certain actions. I have a slider and a dropdown component to simulate the actions.

I am using lodash debounce on the slider. So the exact issue is that I have a state to keep track of the filter with initial value say the category with dog

const initialValue = {
  filters: [
    {
      field: "category",
      values: ["dog"]
    }
  ]
}
const [myFilter, setMyFilter] = useState(initialValue)

With the initial value above, the component will fetch all the pictures with category dog and display on the page. Then I am able to add a dropdown value to filter out say the image size, so then my state output as below:

 myFilter = {
   filters: [
     {
       field: "category",
       values: ["dog"]
     },
     {
       field: "size",
       values: ["500x500"]
     }
   ]
 }

At this point my newest state should as above. I added a debounce for the time range so that the function looks like below

onTimeRangeChange = (value) => {
  // update range state
  setRange(value);
}

const handleSetTimeRange = (value: number[]) => {
    const merge = {
      time_range: {
        start_time: moment(startTime + value[0] * 1000).toISOString(),
        end_time: moment(startTime + value[1] * 1000).toISOString(),
      },
    };

    console.log("myFilter: ", myFilter);
    setMyFilter({ ...myFilter, time_range: merge });
  };

const handleDebounce = useCallback(debounce(handleSetTimeRange, 500), [])

useEffect(() => {
  if(prevRange && oneRange !== prevRange)
  {
     handleDebounce(range)
  }
}, [range])

so myFilter after all is expected to be

  myFilter = {
    filters: [
     {
       field: "category",
       values: ["dog"]
     },
     {
       field: "size",
       values: ["500x500"]
     }
   ],
   time_range: {
        start_time: moment(startTime + value[0] * 1000).toISOString(),
        end_time: moment(startTime + value[1] * 1000).toISOString(),
   }
 }

but instead the actual value is shown as below:

myFilter = {
    filters: [
     {
       field: "category",
       values: ["dog"]
     }
   ],
   time_range: {
        start_time: moment(startTime + value[0] * 1000).toISOString(),
        end_time: moment(startTime + value[1] * 1000).toISOString(),
   }
 }

Somehow it removed the size object under the filters. Im not sure what is actually happening, Im not very familiar with debounce and useCallback. Do they reset the state?

1 Answers

Looks like the useCallback hook has an empty dependency array,

const handleDebounce = useCallback(debounce(handleSetTimeRange, 500), []);

so the initial myFilter state value is closed over in callback scope and never updated.

const handleSetTimeRange = (value: number[]) => {
  const merge = {
    time_range: {
      start_time: moment(startTime + value[0] * 1000).toISOString(),
      end_time: moment(startTime + value[1] * 1000).toISOString(),
    },
  };

  console.log("myFilter: ", myFilter);
  setMyFilter({ ...myFilter, time_range: merge });
};

You can likely either add myFilter to the useCallback hook's dependency array to reenclose the updated myFilter state value in callback scope:

const handleDebounce = useCallback(
  debounce(handleSetTimeRange, 500),
  [myFilter],
);

or use a functional state update to correctly update from any previous state:

const handleSetTimeRange = (value: number[]) => {
  const merge = {
    time_range: {
      start_time: moment(startTime + value[0] * 1000).toISOString(),
      end_time: moment(startTime + value[1] * 1000).toISOString(),
    },
  };

  setMyFilter(myFilter => {
    return {
      ...myFilter,
      time_range: merge
    }
  });
};
Related