@exabytellc/utils
Version:
EB react utils to make everything a little easier!
304 lines (252 loc) • 8.89 kB
JavaScript
'use client';
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { Cookie, LocalStorage, SessionStorage } from "../Storage";
import { isFunc } from "../types";
/**
* @param {boolean} initialValue - Initial boolean state.
* @returns {[boolean, Function, { toggle: Function, reset: Function }]}
*/
export function useToggleState(initialValue = false) {
const [value, setValue] = useState(initialValue);
const initial = useRef(initialValue);
const toggle = useCallback(() => setValue((v) => !v), []);
const reset = useCallback(() => setValue(initial.current), []);
return [value, setValue, { toggle, reset }];
}
/**
* @param {boolean} initialValue - Initial modal state.
* @returns {[boolean, Function, { open: Function, close: Function, toggle: Function, reset: Function }]}
*/
export function useModalState(initialValue = false) {
const [value, setValue, { toggle, reset }] = useToggleState(initialValue);
const open = useCallback(() => setValue(true), []);
const close = useCallback(() => setValue(false), []);
return [value, setValue, { toggle, reset, open, close }];
}
/**
* @param {any} initialValue
* @param {{ name: string, expireHours?: number|null, json?: boolean }} options
* @returns {[any, Function, { save: Function, refresh: Function }]}
*/
export function useCookieState(initialValue, { name, expireHours = null, json = false } = {}) {
const initial = useRef(initialValue);
const [value, setValue] = useState(() => Cookie.get(name, { json }) ?? initialValue);
const save = useCallback((val) => {
if (val != null) {
Cookie.set(name, val, { expireHours, json });
} else {
Cookie.del(name);
}
}, [name, expireHours, json]);
const refresh = useCallback(() => {
setValue(Cookie.get(name, { json }) ?? initial.current);
}, [name, json]);
useEffect(() => {
if (expireHours && value !== initialValue) {
const interval = setInterval(() => {
if (!Cookie.get(name)) setValue(initialValue);
}, expireHours * 3600000 + 1000);
return () => clearInterval(interval);
}
}, [expireHours, name, value]);
return [value, setValue, { save, refresh }];
}
/**
* @param {any} initialValue
* @param {{ name: string, json?: boolean }} options
* @returns {[any, Function, { save: Function }]}
*/
export function useSessionStorageState(initialValue, { name, json = false } = {}) {
const [value, setValue] = useState(() => SessionStorage.get(name, { json }) ?? initialValue);
const save = useCallback((val) => {
if (val != null) {
SessionStorage.set(name, val, { json });
} else {
SessionStorage.del(name);
}
}, [name, json]);
useEffect(() => {
save(value);
}, [value, save]);
return [value, setValue, { save }];
}
/**
* @param {any} initialValue
* @param {{ name: string, json?: boolean }} options
* @returns {[any, Function, { save: Function }]}
*/
export function useLocalStorageState(initialValue, { name, json = false } = {}) {
const [value, setValue] = useState(() => LocalStorage.get(name, { json }) ?? initialValue);
const save = useCallback((val) => {
if (val != null) {
LocalStorage.set(name, val, { json });
} else {
LocalStorage.del(name);
}
}, [name, json]);
useEffect(() => {
save(value);
}, [value, save]);
return [value, setValue, { save }];
}
/**
* @param {Array} initialList
* @returns {[Array, Function, Object]}
*/
export function useListState(initialList = []) {
const [list, setList] = useState(initialList);
const indexWhere = useCallback((where) => list.findIndex(where), [list]);
const findWhere = useCallback((where) => list.find(where), [list]);
const add = useCallback((item) => {
setList((prev) => [...prev, isFunc(item) ? item(prev) : item]);
}, []);
const addAfter = useCallback((item, index) => {
setList((prev) => [
...prev.slice(0, index + 1),
isFunc(item) ? item(prev) : item,
...prev.slice(index + 1),
]);
}, []);
const update = useCallback((item, index) => {
setList((prev) => {
const newList = [...prev];
if (newList[index]) newList[index] = isFunc(item) ? item(prev) : item;
return newList;
});
}, []);
const updateWhere = useCallback((item, where) => {
const i = indexWhere(where);
if (i > -1) update(item, i);
}, [indexWhere, update]);
const remove = useCallback((index) => {
setList((prev) => {
const next = [...prev];
next.splice(index, 1);
return next;
});
}, []);
const removeWhere = useCallback((where) => {
const i = indexWhere(where);
if (i > -1) remove(i);
}, [indexWhere, remove]);
return [list, setList, {
indexWhere, findWhere, add, addAfter, update, updateWhere, remove, removeWhere,
}];
}
/**
* @param {Object} initialMap
* @returns {[Object, Function, Object]}
*/
export function useMapState(initialMap = {}) {
const [map, setMap] = useState(initialMap);
const keys = useMemo(() => Object.keys(map), [map]);
const values = useMemo(() => Object.values(map), [map]);
const keyWhere = useCallback((where) => keys[values.findIndex(where)], [keys, values]);
const findWhere = useCallback((where) => values.find(where), [values]);
const set = useCallback((key, item) => {
setMap((prev) => ({ ...prev, [key]: isFunc(item) ? item(prev) : item }));
}, []);
const update = useCallback((item, key) => {
setMap((prev) => {
const next = { ...prev };
if (next[key]) next[key] = isFunc(item) ? item(prev) : item;
return next;
});
}, []);
const updateWhere = useCallback((item, where) => {
const key = keyWhere(where);
if (key) update(item, key);
}, [keyWhere, update]);
const remove = useCallback((key) => {
setMap((prev) => {
const next = { ...prev };
delete next[key];
return next;
});
}, []);
const removeWhere = useCallback((where) => {
const key = keyWhere(where);
if (key) remove(key);
}, [keyWhere, remove]);
return [map, setMap, {
keys, values, keyWhere, findWhere, set, update, updateWhere, remove, removeWhere,
}];
}
/**
* @param {any} initialValue
* @returns {[any, Function, { setDebounced: Function }]}
*/
export function useDebounceState(initialValue) {
const [value, setValue] = useState(initialValue);
const timeout = useRef(null);
const setDebounced = useCallback((val, delay = 1000) => {
clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
setValue((prev) => isFunc(val) ? val(prev) : val);
}, delay);
}, []);
return [value, setValue, { setDebounced }];
}
/**
* @param {string} query
* @returns {[boolean, Function]}
*/
export function useMediaQueryState(query) {
const [matches, setMatches] = useState(() => window.matchMedia(query).matches);
useEffect(() => {
const media = window.matchMedia(query);
const update = () => setMatches(media.matches);
media.addEventListener("change", update);
return () => media.removeEventListener("change", update);
}, [query]);
return [matches, setMatches];
}
/**
* @param {Object} queries
* @returns {[Object, Function, { ifQuery: Function, ifQueries: Function, breakpointValue: Function }]}
*/
export function useMediaQueriesState(queries) {
const [state, setState] = useState({});
useEffect(() => {
const media = {};
const update = () => {
const next = {};
for (const key in queries) {
next[key] = media[key].matches;
}
setState(next);
};
for (const key in queries) {
media[key] = window.matchMedia(queries[key]);
media[key].addEventListener("change", update);
}
update();
return () => {
for (const key in media) {
media[key].removeEventListener("change", update);
}
};
}, [queries]);
const ifQuery = useCallback((key) => !!state[key], [state]);
const ifQueries = useCallback((keys, logic = "OR") => {
if (logic === "AND") return keys.every((k) => state[k]);
return keys.some((k) => state[k]);
}, [state]);
const breakpointValue = useCallback((...args) => {
const keys = Object.keys(queries);
if (typeof args[0] === "object" && !Array.isArray(args[0])) {
args = keys.map((k) => args[0][k] ?? null);
}
for (let i = keys.length - 1; i >= 0; i--) {
if (state[keys[i]] && args[i] != null) return args[i];
}
return args.find((v) => v != null);
}, [state, queries]);
return [state, setState, { ifQuery, ifQueries, breakpointValue }];
}