2022-05-31 15:49:04 +00:00
|
|
|
import { SelectorOption, SelectorProps } from "@/components";
|
|
|
|
import { SliderProps } from "@mantine/core";
|
|
|
|
import {
|
|
|
|
Dispatch,
|
|
|
|
useCallback,
|
|
|
|
useEffect,
|
|
|
|
useMemo,
|
|
|
|
useRef,
|
|
|
|
useState,
|
|
|
|
} from "react";
|
2022-03-16 06:26:15 +00:00
|
|
|
import { useNavigate } from "react-router-dom";
|
2021-03-25 14:22:43 +00:00
|
|
|
|
2021-03-29 11:16:45 +00:00
|
|
|
export function useGotoHomepage() {
|
2022-03-16 06:26:15 +00:00
|
|
|
const navigate = useNavigate();
|
|
|
|
return useCallback(() => navigate("/"), [navigate]);
|
2021-03-29 11:16:45 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
export function useSelectorOptions<T>(
|
|
|
|
options: readonly T[],
|
|
|
|
label: (value: T) => string,
|
|
|
|
key?: (value: T) => string
|
|
|
|
): Pick<SelectorProps<T>, "options" | "getkey"> {
|
|
|
|
const labelRef = useRef(label);
|
|
|
|
labelRef.current = label;
|
|
|
|
|
|
|
|
const keyRef = useRef(key);
|
|
|
|
keyRef.current = key;
|
|
|
|
|
|
|
|
const wrappedOptions = useMemo(
|
|
|
|
() =>
|
|
|
|
options.map<SelectorOption<T>>((value) => ({
|
|
|
|
value,
|
|
|
|
label: labelRef.current(value),
|
|
|
|
})),
|
|
|
|
[options]
|
|
|
|
);
|
|
|
|
|
|
|
|
return useMemo(
|
|
|
|
() => ({
|
|
|
|
options: wrappedOptions,
|
|
|
|
getkey: keyRef.current ?? labelRef.current,
|
|
|
|
}),
|
|
|
|
[wrappedOptions]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useSliderMarks(values: number[]): SliderProps["marks"] {
|
|
|
|
return useMemo<SliderProps["marks"]>(
|
|
|
|
() =>
|
|
|
|
values.map((value) => ({
|
|
|
|
value: value,
|
|
|
|
label: value.toString(),
|
|
|
|
})),
|
|
|
|
[values]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// High performance action wrapper for array, typically used for table updates
|
|
|
|
export function useArrayAction<T>(setData: Dispatch<(prev: T[]) => T[]>) {
|
|
|
|
const setDataRef = useRef(setData);
|
|
|
|
setDataRef.current = setData;
|
|
|
|
|
|
|
|
const add = useCallback((row: T) => {
|
|
|
|
setDataRef.current((data) => {
|
|
|
|
return [...data, row];
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const mutate = useCallback((index: number, row: T) => {
|
|
|
|
setDataRef.current((data) => {
|
|
|
|
if (index !== -1) {
|
|
|
|
const list = [...data];
|
|
|
|
list[index] = row;
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const remove = useCallback((index: number) => {
|
|
|
|
setDataRef.current((data) => {
|
|
|
|
if (index !== -1) {
|
|
|
|
const list = [...data];
|
|
|
|
list.splice(index, 1);
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const update = useCallback((fn: (item: T) => T) => {
|
|
|
|
setDataRef.current((data) => {
|
|
|
|
return data.map(fn);
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
return useMemo(
|
|
|
|
() => ({
|
|
|
|
add,
|
|
|
|
mutate,
|
|
|
|
remove,
|
|
|
|
update,
|
|
|
|
}),
|
|
|
|
[add, mutate, remove, update]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useThrottle<F extends GenericFunction>(fn: F, ms: number) {
|
|
|
|
const fnRef = useRef(fn);
|
|
|
|
fnRef.current = fn;
|
|
|
|
|
|
|
|
const timer = useRef<number>();
|
|
|
|
|
|
|
|
return useCallback(
|
|
|
|
(...args: Parameters<F>) => {
|
|
|
|
if (timer.current) {
|
|
|
|
clearTimeout(timer.current);
|
|
|
|
timer.current = undefined;
|
|
|
|
}
|
|
|
|
timer.current = window.setTimeout(() => fnRef.current(...args), ms);
|
|
|
|
},
|
|
|
|
[ms]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useDebouncedValue<T>(item: T, ms: number) {
|
|
|
|
const [value, setValue] = useState(item);
|
|
|
|
|
|
|
|
const debouncedSetValue = useThrottle(setValue, ms);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
debouncedSetValue(item);
|
|
|
|
}, [debouncedSetValue, item]);
|
|
|
|
|
|
|
|
return value;
|
2021-08-16 14:48:00 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
export function useOnValueChange<T>(value: T, onChange: (value: T) => void) {
|
|
|
|
const valueRef = useRef<T | null>(null);
|
2021-08-15 16:10:38 +00:00
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
const onChangeRef = useRef(onChange);
|
|
|
|
onChangeRef.current = onChange;
|
2021-08-15 16:10:38 +00:00
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
useEffect(() => {
|
|
|
|
if (valueRef.current !== value) {
|
|
|
|
valueRef.current = value;
|
|
|
|
onChangeRef.current(value);
|
|
|
|
}
|
|
|
|
}, [value]);
|
2021-08-15 16:10:38 +00:00
|
|
|
}
|
2022-06-14 10:08:31 +00:00
|
|
|
|
|
|
|
// Mantine's useInterval has some weird issues. This is a workaround.
|
|
|
|
export function useInterval(fn: VoidFunction, ms: number) {
|
|
|
|
const timer = useRef<number>();
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
timer.current = window.setInterval(fn, ms);
|
|
|
|
return () => {
|
|
|
|
clearInterval(timer.current);
|
|
|
|
};
|
|
|
|
}, [fn, ms]);
|
|
|
|
}
|