66 lines
1.7 KiB
TypeScript
66 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
import { Range } from "react-date-range";
|
|
|
|
interface IUseDebouncedDateRangeOptions {
|
|
initialDateRange?: Range[];
|
|
debounceMs?: number;
|
|
onDateRangeChange?: (range: Range[]) => void | Promise<void>;
|
|
skipInitialFetch?: boolean;
|
|
}
|
|
|
|
export const useDebouncedDateRange = ({
|
|
initialDateRange,
|
|
debounceMs = 1000,
|
|
onDateRangeChange,
|
|
skipInitialFetch = false,
|
|
}: IUseDebouncedDateRangeOptions = {}) => {
|
|
const [dateRange, setDateRange] = useState<Range[]>(initialDateRange ?? []);
|
|
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
const isFirstMount = useRef(true);
|
|
|
|
const handleDateRangeChange = useCallback(
|
|
(newRange: Range[]) => {
|
|
// Update state immediately for UI responsiveness
|
|
setDateRange(newRange);
|
|
|
|
// Clear any existing debounce timeout
|
|
if (debounceTimeoutRef.current) {
|
|
clearTimeout(debounceTimeoutRef.current);
|
|
}
|
|
|
|
// Skip fetch on first mount if requested
|
|
if (isFirstMount.current) {
|
|
isFirstMount.current = false;
|
|
if (skipInitialFetch) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const currentRange = newRange[0];
|
|
if (!currentRange?.startDate || !currentRange?.endDate) return;
|
|
|
|
// Debounce the callback
|
|
debounceTimeoutRef.current = setTimeout(() => {
|
|
onDateRangeChange?.(newRange);
|
|
}, debounceMs);
|
|
},
|
|
[onDateRangeChange, debounceMs, skipInitialFetch]
|
|
);
|
|
|
|
// Cleanup timeout on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
if (debounceTimeoutRef.current) {
|
|
clearTimeout(debounceTimeoutRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
dateRange,
|
|
handleDateRangeChange,
|
|
};
|
|
};
|