UNPKG

@exabytellc/utils

Version:

EB react utils to make everything a little easier!

304 lines (252 loc) 8.89 kB
'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 }]; }