@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
381 lines (363 loc) • 9.71 kB
JavaScript
;
/**
* React hook utilities for common patterns and state management
*/
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
/**
* Hook for managing async operations with loading, error, and data states
*/
export function useAsync(asyncFn, deps = []) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const execute = useCallback(async () => {
setLoading(true);
setError(null);
try {
const result = await asyncFn();
setData(result);
return result;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
setError(error);
throw error;
} finally {
setLoading(false);
}
}, deps);
return {
data,
loading,
error,
execute
};
}
/**
* Hook for managing async operations that execute on mount
*/
export function useAsyncEffect(asyncFn, deps = []) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let mounted = true;
const execute = async () => {
try {
const result = await asyncFn();
if (mounted) {
setData(result);
setLoading(false);
}
} catch (err) {
if (mounted) {
const error = err instanceof Error ? err : new Error(String(err));
setError(error);
setLoading(false);
}
}
};
execute();
return () => {
mounted = false;
};
}, deps);
return {
data,
loading,
error
};
}
/**
* Hook for debounced values
*/
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
/**
* Hook for throttled values
*/
export function useThrottle(value, delay) {
const [throttledValue, setThrottledValue] = useState(value);
const lastRun = useRef(Date.now());
useEffect(() => {
const handler = setTimeout(() => {
if (Date.now() - lastRun.current >= delay) {
setThrottledValue(value);
lastRun.current = Date.now();
}
}, delay - (Date.now() - lastRun.current));
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return throttledValue;
}
/**
* Hook for previous value
*/
export function usePrevious(value) {
const ref = useRef(undefined);
useEffect(() => {
ref.current = value;
});
return ref.current;
}
/**
* Hook for boolean state with toggle
*/
export function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return {
value,
toggle,
setTrue,
setFalse,
setValue
};
}
/**
* Hook for counter state
*/
export function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => setCount(c => c + 1), []);
const decrement = useCallback(() => setCount(c => c - 1), []);
const reset = useCallback(() => setCount(initialValue), [initialValue]);
const setValue = useCallback(value => setCount(value), []);
return {
count,
increment,
decrement,
reset,
setValue
};
}
/**
* Hook for local storage
*/
export function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
const setValue = useCallback(value => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
}, [key, storedValue]);
return [storedValue, setValue];
}
/**
* Hook for session storage
*/
export function useSessionStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.sessionStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading sessionStorage key "${key}":`, error);
return initialValue;
}
});
const setValue = useCallback(value => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting sessionStorage key "${key}":`, error);
}
}, [key, storedValue]);
return [storedValue, setValue];
}
/**
* Hook for window size
*/
export function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
/**
* Hook for scroll position
*/
export function useScrollPosition() {
const [scrollPosition, setScrollPosition] = useState(0);
useEffect(() => {
const handleScroll = () => {
setScrollPosition(window.pageYOffset);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return scrollPosition;
}
/**
* Hook for online/offline status
*/
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
/**
* Hook for media queries
*/
export function useMediaQuery(query) {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => setMatches(media.matches);
media.addEventListener('change', listener);
return () => media.removeEventListener('change', listener);
}, [matches, query]);
return matches;
}
/**
* Hook for keyboard events
*/
export function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
useEffect(() => {
const downHandler = ({
key
}) => {
if (key === targetKey) {
setKeyPressed(true);
}
};
const upHandler = ({
key
}) => {
if (key === targetKey) {
setKeyPressed(false);
}
};
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
}, [targetKey]);
return keyPressed;
}
/**
* Hook for click outside detection
*/
export function useClickOutside(ref, handler) {
useEffect(() => {
const listener = event => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler();
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]);
}
/**
* Hook for form validation
*/
export function useFormValidation(initialValues, validationSchema) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const validate = useCallback(valuesToValidate => {
return validationSchema(valuesToValidate);
}, [validationSchema]);
const setValue = useCallback((field, value) => {
setValues(prev => ({
...prev,
[field]: value
}));
if (touched[field]) {
const newErrors = validate({
...values,
[field]: value
});
setErrors(prev => ({
...prev,
[field]: newErrors[field]
}));
}
}, [values, touched, validate]);
const setTouchedField = useCallback(field => {
setTouched(prev => ({
...prev,
[field]: true
}));
const newErrors = validate(values);
setErrors(prev => ({
...prev,
[field]: newErrors[field]
}));
}, [values, validate]);
const isValid = useMemo(() => {
const validationErrors = validate(values);
return Object.keys(validationErrors).length === 0;
}, [values, validate]);
return {
values,
errors,
touched,
isValid,
setValue,
setTouchedField,
setValues,
setErrors,
setTouched
};
}
//# sourceMappingURL=hookUtils.js.map