Custom hooks
Data Injection + useAction
dev.to/react-custom-hook-usearray-1ogp | github.com/react-custom-hooks
useArray
useAsync
useClickOutside
useCookie
useCopyToClipboard
useDarkMode
useDebounce
useDebugInformation
useDeepCompareEffect
useEffectOnce
useEventListener
useFetch
useGeolocation
useHover
useLongPress
useMediaQuery
useOnlineStatus
useOnScreen
usePrevious
useRenderCount
State
functional setState
Code
interface MyState {
loading: boolean;
data: any;
something: string;
}
const [state, setState] = useReducer<Reducer<MyState, Partial<MyState>>>(
(state, newState) => ({...state, ...newState}),
{loading: true, data: null, something: ''}
)
useState with a stable link
function useAsyncReference(value, isProp = false) {
const ref = useRef(value);
const [, forceRender] = useState(false);
function updateState(newState) {
if (!Object.is(ref.current, newState)) {
ref.current = newState;
forceRender(s => !s);
}
}
if (isProp) {
ref.current = value;
return ref;
}
return [ref, updateState];
}
function useAsyncReference(value) { ... }
function Counter() {
const [count, setCount] = useAsyncReference(0);
function handleAlertClick() {
setTimeout(() => {
alert("You clicked on: " + count.current);
}, 3000);
}
return (
<div>
<p>You clicked {count.current} times</p>
<button
onClick={() => {
setCount(count.current + 1);
}}
>
Click me
</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
}
Lifecicle
useWillMount
Code
function useWillMount(callback) {
const ref = useRef();
if (!ref.current) {
ref.current = {
value: callback()
};
}
return ref.current.value;
}
Closure
useEvent (useCallback analog)
function App() {
const [text, setText] = useState("");
const onClick = useEvent(() => { // <== stable link
console.log("test:", text);
});
}
useDeepCompareEffect (useEffect)
Code
function App() {
const [count, setCount] = useState(0);
const [counterObj, setCounterObj] = useState({ firstCount: 0 });
const [secondObj, setSecondObj] = useState({ secCount: 0 });
useDeepCompareEffect(() => {
console.log("counterObj", [count], counterObj, secondObj);
}, [counterObj]);
}
Forms
useInput
Code
export default function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
const onChange = e => {
setValue(e.target.value)
}
return {
value, onChange
}
};
Network, geo
useLocation
предоставляет данные о геолокации в реальном времени, а также метрики курса движения и скорости. 🗺️📍 Демо: https://grayscal.es/hooks/use-location
Используйте его для получения информации о близлежащих местах или для определения местоположения пользователей поблизости.
https://gist.github.com/KristofferEriksson/ee5af0a52e1fea0acc028e9b10aa0969
import { useEffect, useState } from "react";
interface LocationOptions {
enableHighAccuracy?: boolean;
timeout?: number;
maximumAge?: number;
}
interface LocationState {
coords: {
latitude: number | null;
longitude: number | null;
accuracy: number | null;
altitude: number | null;
altitudeAccuracy: number | null;
heading: number | null;
speed: number | null;
};
locatedAt: number | null;
error: string | null;
}
const useLocation = (options: LocationOptions = {}) => {
const [location, setLocation] = useState<LocationState>({
coords: {
latitude: null,
longitude: null,
accuracy: null,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null,
},
locatedAt: null,
error: null,
});
useEffect(() => {
// Ensuring this runs only in a client-side environment
if (typeof window === "undefined" || !("geolocation" in navigator)) {
setLocation((prevState) => ({
...prevState,
error:
"Geolocation is not supported by your browser or not available in the current environment",
}));
return;
}
const handleSuccess = (position: GeolocationPosition) => {
setLocation({
coords: {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude,
altitudeAccuracy: position.coords.altitudeAccuracy,
heading: position.coords.heading,
speed: position.coords.speed,
},
locatedAt: position.timestamp,
error: null,
});
};
const handleError = (error: GeolocationPositionError) => {
setLocation((prevState) => ({
...prevState,
error: error.message,
}));
};
const geoOptions = {
enableHighAccuracy: options.enableHighAccuracy || false,
timeout: options.timeout || Infinity,
maximumAge: options.maximumAge || 0,
};
const watcher = navigator.geolocation.watchPosition(
handleSuccess,
handleError,
geoOptions,
);
return () => navigator.geolocation.clearWatch(watcher);
}, [options.enableHighAccuracy, options.timeout, options.maximumAge]);
return location;
};
export default useLocation;
useNetwork
Вы можете узнать скорость передачи данных, тип соединения и статус сети прямо в вашем приложении. Demo: https://grayscal.es/hooks/use-network
https://gist.github.com/KristofferEriksson/7e3de25dfa587943abc04841f57d635c
import { useEffect, useState } from "react";
interface NetworkInformation extends EventTarget {
downlink?: number;
effectiveType?: "slow-2g" | "2g" | "3g" | "4g";
rtt?: number;
saveData?: boolean;
onchange?: EventListener;
}
interface NavigatorWithNetworkInformation extends Navigator {
connection?: NetworkInformation;
}
interface NetworkState {
downlink?: number | null;
effectiveType?: "slow-2g" | "2g" | "3g" | "4g" | null;
rtt?: number | null;
saveData?: boolean | null;
isOnline: boolean;
}
function useNetwork(): NetworkState {
const [networkState, setNetworkState] = useState<NetworkState>({
downlink: null,
effectiveType: null,
rtt: null,
saveData: null,
isOnline: false, // Default to false; updated correctly on the client-side
});
useEffect(() => {
// Ensure we are in the browser environment
if (typeof window === "undefined") {
return;
}
const nav = navigator as NavigatorWithNetworkInformation;
if (!nav.connection) {
setNetworkState((prevState) => ({
...prevState,
downlink: null,
effectiveType: null,
rtt: null,
saveData: null,
isOnline: navigator.onLine, // Update online status in the browser
}));
return;
}
const updateNetworkInfo = () => {
const { downlink, effectiveType, rtt, saveData } = nav.connection!;
setNetworkState((prevState) => ({
...prevState,
downlink,
effectiveType,
rtt,
saveData,
isOnline: navigator.onLine,
}));
};
updateNetworkInfo();
nav.connection!.addEventListener("change", updateNetworkInfo);
window.addEventListener("online", () =>
setNetworkState((prevState) => ({ ...prevState, isOnline: true })),
);
window.addEventListener("offline", () =>
setNetworkState((prevState) => ({ ...prevState, isOnline: false })),
);
return () => {
if (nav.connection) {
nav.connection.removeEventListener("change", updateNetworkInfo);
}
window.removeEventListener("online", () =>
setNetworkState((prevState) => ({ ...prevState, isOnline: true })),
);
window.removeEventListener("offline", () =>
setNetworkState((prevState) => ({ ...prevState, isOnline: false })),
);
};
}, []);
return networkState;
}
export default useNetwork;
Misc
useExternalScript
const { isLoading, isError } = useExternalScript({
url,
onSuccess: () => . . . do smth . . .
});
import { useEffect, useState } from 'react';
type Hook = (props: { url: string; onSuccess?: () => void }) => { isLoading: boolean; isError: boolean };
export const useExternalScript: Hook = ({ url, onSuccess }) => {
const [isLoading, setIsLoading] = useState(!!url);
const [isError, setI const { isLoading: isScriptLoading, isError: isScriptLoadingError } = useExternalScript({
url,
onSuccess: () => window.encryptData || setIsError(true),
});sError] = useState(false);
useEffect(() => {
if (!url) {
return;
}
let script: HTMLScriptElement | null = document.querySelector(`script[src="${url}"]`);
if (script) {
setIsLoading(false);
return;
}
const handleScript = (e: Event) => {
if (e.type === 'load') {
setIsLoading(false);
onSuccess && onSuccess();
}
e.type !== 'load' && setIsError(true);
};
script = document.createElement('script');
script.type = 'application/javascript';
script.src = url;
script.setAttribute('data-testid', 'external-script');
script.async = true;
document.body.appendChild(script);
script.addEventListener('load', handleScript);
script.addEventListener('error', handleScript);
script.addEventListener('load', handleScript);
script.addEventListener('error', handleScript);
return () => {
script?.removeEventListener('load', handleScript);
script?.removeEventListener('error', handleScript);
};
}, [onSuccess, url]);
return { isLoading, isError };
};
useScroll
Code
export default function useScroll(parentRef, childRef, callback) {
const observer = useRef();
useEffect(() => {
const options = {
root: parentRef.current,
rootMargin: '0px',
threshold: 0
}
observer.current = new IntersectionObserver(([target]) => {
if (target.isIntersecting) {
console.log('intersected')
callback()
}
}, options)
observer.current.observe(childRef.current)
return function () {
observer.current.unobserve(childRef.current)
};
}, [callback])
};
Selection
useTextSelection
https://t.me/react_tg/417 отслеживает выделение текста пользователем и его положение на экране! Демо: http://grayscal.es/hooks/use-text-selection
Идеально подходит для всплывающая подсказка "Поделиться", подобно Medium. ✨
https://gist.github.com/KristofferEriksson/8acb9b3eb241507eb0f6232938bf4ec7
import { useEffect, useState } from "react";
type UseTextSelectionReturn = {
text: string;
rects: DOMRect[];
ranges: Range[];
selection: Selection | null;
};
const getRangesFromSelection = (selection: Selection): Range[] => {
const rangeCount = selection.rangeCount;
return Array.from({ length: rangeCount }, (_, i) => selection.getRangeAt(i));
};
const isSelectionInsideRef = (
selection: Selection,
ref: React.RefObject<HTMLElement>,
) => {
if (!ref.current || selection.rangeCount === 0) return true;
const range = selection.getRangeAt(0);
return ref.current.contains(range.commonAncestorContainer);
};
export function useTextSelection(
elementRef?: React.RefObject<HTMLElement>,
): UseTextSelectionReturn {
const [selection, setSelection] = useState<Selection | null>(null);
const [text, setText] = useState("");
const [ranges, setRanges] = useState<Range[]>([]);
const [rects, setRects] = useState<DOMRect[]>([]);
useEffect(() => {
const onSelectionChange = () => {
const newSelection = window.getSelection();
if (
newSelection &&
(!elementRef || isSelectionInsideRef(newSelection, elementRef))
) {
setSelection(newSelection);
setText(newSelection.toString());
const newRanges = getRangesFromSelection(newSelection);
setRanges(newRanges);
setRects(newRanges.map((range) => range.getBoundingClientRect()));
} else {
setText("");
setRanges([]);
setRects([]);
setSelection(null);
}
};
document.addEventListener("selectionchange", onSelectionChange);
return () => {
document.removeEventListener("selectionchange", onSelectionChange);
};
}, [elementRef]);
return {
text,
rects,
ranges,
selection,
};
}
Last updated
Was this helpful?