@alessiofrittoli/react-hooks
v4.2.2
Published
TypeScript React utility Hooks
Readme
Table of Contents
- Getting started
- ESLint Configuration
- What's Changed
- API Reference
- Development
- Contributing
- Security
- Credits
Getting started
Run the following command to start using react-hooks in your projects:
npm i @alessiofrittoli/react-hooksor using pnpm
pnpm i @alessiofrittoli/react-hooksESLint Configuration
This library may define and exports hooks that requires additional ESLint configuration for your project such as useUpdateEffect.
Simply imports recommended configuration from @alessiofrittoli/react-hooks/eslint and add them to your ESLint configuration like so:
import { config as AFReactHooksEslint } from "@alessiofrittoli/react-hooks/eslint";
/** @type {import('eslint').Linter.Config[]} */
const config = [
...AFReactHooksEslint.recommended,
// ... other configurations
];
export default config;What's Changed
Updates in the latest release 🎉
- Added
useQueuehook. See API Reference for more info. - Added
useShufflehook. See API Reference for more info.
Old major updates
- Added
usePreventContextMenuhook. See API Reference for more info. - Improved
useConnectionhook. It now returnsNetworkInformationwhen available. See API Reference for more info. - Improved
useEventListenerhook types. It now supportsEventTargetas listener targets. See API Reference for more info. - Added
useDocumentVisibilityhook. See API Reference for more info. - Added
useWakeLockhook. See API Reference for more info. - Added
useDeferCallbackhook. See API Reference for more info.
API Reference
Browser API
Storage
The following storage hooks use Storage Utilities from @alessiofrittoli/web-utils adding a React oriented implementation.
useStorage
Easly handle Local or Session Storage State.
| Parameter | Type | Default | Description |
| --------- | ----- | -------- | ----------------------------------------- |
| T | any | string | A custom type applied to the stored item. |
| Parameter | Type | Default | Description |
| --------- | ---------------- | ------- | ---------------------------------- |
| key | string | - | The storage item key. |
| initial | T | - | The storage item initial value. |
| type | local\|session | local | (Optional) The storage API to use. |
Type: [ Value<T>, SetValue<Value<T>> ]
A tuple with the stored item value or initial value and the setter function.
Importing the hooks
import {
useStorage,
useLocalStorage,
useSessionStorage,
} from "@alessiofrittoli/react-hooks";Reading item value from storage
'use client'
import { useStorage } from '@alessiofrittoli/react-hooks'
type Locale = 'it' | 'en'
const storage = 'local' // or 'session'
const defaultLocale = 'it'
export const SomeComponent: React.FC = () => {
const [ userLocale ] = useStorage<Locale>( 'user-locale', defaultLocale, storage )
return (
...
)
}Updating storage item value
'use client'
import { useCallback } from 'react'
import { useStorage } from '@alessiofrittoli/react-hooks'
type Locale = 'it' | 'en'
const storage = 'local' // or 'session'
const defaultLocale = 'it'
export const LanguageSwitcher: React.FC = () => {
const [ userLocale, setUserLocale ] = useStorage<Locale>( 'user-locale', defaultLocale, storage )
const clickHandler = useCallback( () => {
setUserLocale( 'en' )
}, [ setUserLocale ] )
return (
...
)
}Deleting storage item
'use client'
import { useCallback } from 'react'
import { useStorage } from '@alessiofrittoli/react-hooks'
type Locale = 'it' | 'en'
const storage = 'local' // or 'session'
const defaultLocale = 'it'
export const LanguageSwitcher: React.FC = () => {
const [ userLocale, setUserLocale ] = useStorage<Locale>( 'user-locale', defaultLocale, storage )
const deleteHandler = useCallback( () => {
setUserLocale( null )
// or
setUserLocale( undefined )
// or
setUserLocale( '' )
}, [ setUserLocale ] )
return (
...
)
}useLocalStorage
Shortcut React Hook for useStorage.
Applies the same API Reference.
useSessionStorage
Shortcut React Hook for useStorage.
Applies the same API Reference.
useConnection
Get states about Internet Connection.
Type: Connection
An object defining network status and NetworkInformation.
- See
Connectioninterface from@alessiofrittoli/web-utils
useDarkMode
Easily manage dark mode with full respect for user device preferences.
This hook is user-oriented and built to honor system-level color scheme settings:
- If the device prefers a dark color scheme, dark mode is automatically enabled on first load.
- If the user enables/disables dark mode via a web widget, the preference is stored in
localStorageunder the keydark-mode. - If the device color scheme preference changes (e.g. via OS settings), that change takes precedence and is stored for future visits.
| Parameter | Type | Description |
| ----------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| options | UseDarkModeOptions | (Optional) Configuration object for the hook. |
| options.initial | boolean | (Optional) The fallback value to use if no preference is saved in localStorage. Defaults to true if the device prefers dark mode. |
| options.docClassNames | [dark: string, light: string] | (Optional) Array of class names to toggle on the <html> element, e.g. ['dark', 'light']. |
Type: UseDarkModeOutput
An object containing utilities for managing dark mode:
isDarkMode:boolean— Whether dark mode is currently enabled.isDarkOS:boolean— Whether the user's system prefers dark mode.toggleDarkMode:() => void— Toggles dark mode and saves the preference.enableDarkMode:() => void— Enables dark mode and saves the preference.disableDarkMode:() => void— Disables dark mode and saves the preference.
Basic usage
"use client";
import { useDarkMode } from "@alessiofrittoli/react-hooks";
export const Component: React.FC = () => {
const { isDarkMode } = useDarkMode();
return <div>{isDarkMode ? "Dark mode enabled" : "Dark mode disabled"}</div>;
};Update Document class names for CSS styling
// Component.tsx
"use client";
import { useDarkMode } from "@alessiofrittoli/react-hooks";
export const Component: React.FC = () => {
const { isDarkMode } = useDarkMode({
docClassNames: ["dark", "light"],
});
return <div>{isDarkMode ? "Dark mode enabled" : "Dark mode disabled"}</div>;
};/* style.css */
.light {
color-scheme: light;
}
.dark {
color-scheme: dark;
}
.light body {
color: black;
background: white;
}
.dark body {
color: white;
background: black;
}Custom theme switcher
"use client";
import { useDarkMode } from "@alessiofrittoli/react-hooks";
export const ThemeSwitcher: React.FC = () => {
const { isDarkMode, toggleDarkMode } = useDarkMode();
return <button onClick={toggleDarkMode}>{isDarkMode ? "🌙" : "☀️"}</button>;
};Sync Document theme-color for consistent browser styling
Browsers automatically apply colorization using:
<meta name="theme-color" media="(prefers-color-scheme: dark)" />This works based on the OS preference — not your site theme. That can cause mismatches if, for example, the system is in dark mode but the user disabled dark mode via a web toggle.
To ensure consistency, useDarkMode updates these meta tags dynamically based on the actual mode.
Just make sure to define both light and dark theme-color tags in your document:
<head>
<meta
name="theme-color"
media="(prefers-color-scheme: light)"
content="lime"
/>
<meta
name="theme-color"
media="(prefers-color-scheme: dark)"
content="aqua"
/>
</head>useEventListener
Attach a new Event listener to the Window, Document, MediaQueryList or an HTMLElement.
| Parameter | Type | Description |
| ------------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | K\|K[] | The Window event name or an array of event names. |
| | | ⚠️ Please, make sure to memoize the event names array with useMemo |
| | | or declare that array outside your Component/hook in order to avoid infinite loops when a React state changes. |
| options | WindowListenerOptions<K> | An object defining init options. |
| options.listener | WindowEventListener<K> | The Window Event listener. |
| options.onLoad | () => void | A custom callback executed before event listener get attached. |
| options.onCleanUp | () => void | A custom callback executed after event listener get removed. |
| options.options | ListenerOptions | Specifies characteristics about the event listener. See MDN Reference. |
| Parameter | Type | Description |
| ------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | K\|K[] | The Document event name or an array of event names. |
| | | ⚠️ Please, make sure to memoize the event names array with useMemo |
| | | or declare that array outside your Component/hook in order to avoid infinite loops when a React state changes. |
| options | DocumentListenerOptions<K> | An object defining init options. |
| options.target | Document\|null\|React.RefObject<Document\|null> | The Document reference or a React RefObject of the Document. |
| options.listener | DocumentEventListener<K> | The Document Event listener. |
| options.onLoad | () => void | A custom callback executed before event listener get attached. |
| options.onCleanUp | () => void | A custom callback executed after event listener get removed. |
| options.options | ListenerOptions | Specifies characteristics about the event listener. See MDN Reference. |
| Parameter | Type | Description |
| ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | K\|K[] | The HTMLElement event name or an array of event names. |
| | | ⚠️ Please, make sure to memoize the event names array with useMemo |
| | | or declare that array outside your Component/hook in order to avoid infinite loops when a React state changes. |
| options | ElementListenerOptions<K> | An object defining init options. |
| options.target | T\|React.RefObject<T\| null> | The React RefObject of the target where the listener get attached to. |
| options.listener | ElementEventListener<K> | The HTMLElement Event listener. |
| options.onLoad | () => void | A custom callback executed before event listener get attached. |
| options.onCleanUp | () => void | A custom callback executed after event listener get removed. |
| options.options | ListenerOptions | Specifies characteristics about the event listener. See MDN Reference. |
| Parameter | Type | Description |
| ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | change | The MediaQueryList event name. |
| options | MediaQueryListenerOptions | An object defining init options. |
| options.query | string | The Media Query string to check. |
| options.listener | MediaQueryChangeListener | The MediaQueryList Event listener. |
| options.onLoad | () => void | A custom callback executed before event listener get attached. |
| options.onCleanUp | () => void | A custom callback executed after event listener get removed. |
| options.options | ListenerOptions | Specifies characteristics about the event listener. See MDN Reference. |
| Parameter | Type | Description |
| ------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | K\|K[] | The custom event name or an array of event names. |
| | | ⚠️ Please, make sure to memoize the event names array with useMemo |
| | | or declare that array outside your Component/hook in order to avoid infinite loops when a React state changes. |
| options | CustomEventListenerOptions<T, K> | An object defining init options. |
| options.target | Document\|EventTarget\|HTMLElement\|null\|React.RefObject<Document\|HTMLElement\|null> | (Optional) The target where the listener get attached to. If not set, the listener will get attached to the Window object. |
| options.listener | ( event: T[ K ] ) => void | The Event listener. |
| options.onLoad | () => void | A custom callback executed before event listener get attached. |
| options.onCleanUp | () => void | A custom callback executed after event listener get removed. |
| options.options | ListenerOptions | Specifies characteristics about the event listener. See MDN Reference. |
Attach listeners to the Window object
'use client'
import { useCallback } from 'react'
import { useEventListener } from '@alessiofrittoli/react-hooks'
export const MyComponent: React.FC = () => {
useEventListener( 'popstate', {
listener: useCallback( event => {
...
}, [] ),
} )
}Attach listeners to the Document object
'use client'
import { useCallback } from 'react'
import { useEventListener } from '@alessiofrittoli/react-hooks'
export const MyComponent: React.FC = () => {
useEventListener( 'click', {
target : typeof document !== 'undefined' ? document : null,
listener : useCallback( event => {
...
}, [] ),
} )
}Attach listeners to an HTMLElement
'use client'
import { useCallback, useRef } from 'react'
import { useEventListener } from '@alessiofrittoli/react-hooks'
export const MyComponent: React.FC = () => {
const buttonRef = useRef<HTMLButtonElement>( null )
useEventListener( 'click', {
target: buttonRef,
listener: useCallback( event => {
...
}, [] ),
} )
return (
<button ref={ buttonRef }>Button</button>
)
}Attach listeners to a MediaQueryList
import { useCallback } from 'react'
import { useEventListener } from '@alessiofrittoli/react-hooks'
export const MyComponent: React.FC = () => {
useEventListener( 'change', {
query : '(max-width: 768px)',
listener : useCallback( event => {
if ( event.matches ) {
...
}
}, [] )
} )
}Listen dispatched custom events
import { useCallback } from 'react'
import { useEventListener } from '@alessiofrittoli/react-hooks'
class CustomEvent extends Event
{
isCustom: boolean
constructor( type: string, eventInitDict?: EventInit )
{
super( type, eventInitDict )
this.isCustom = true
}
}
type CustomEventMap = {
customEventName: CustomEvent
}
export const MyComponent: React.FC = () => {
const clickHandler = useCallback( () => {
document.dispatchEvent( new CustomEvent( 'customEventName' ) )
}, [] )
useEventListener<CustomEventMap>( 'customEventName', {
target : typeof document !== 'undefined' ? document : null,
listener : useCallback( event => {
if ( event.isCustom ) {
...
}
}, [] )
} )
return (
<button onClick={ clickHandler }>Click me to dispatch custom event</button>
)
}Attach listeners to multiple events
import { useCallback, useState } from "react";
import { useEventListener } from "@alessiofrittoli/react-hooks";
/**
* We define events outside the Component to avoid array recreation when a state update is triggered.
*
* This prevents infinite loops for `useEventListener` life-cycle
*/
const events: (keyof WindowEventMap)[] = ["resize", "scroll"];
export const MyComponent: React.FC = () => {
const [isInteracting, setIsIntercting] = useState(false);
useEventListener(events, {
listener: useCallback(() => {
setIsIntercting(true);
}, []),
});
};useIsPortrait
Check if device is portrait oriented.
React State get updated when device orientation changes.
Type: boolean
trueif the device is portrait oriented.falseotherwise.
Check if user device is in landscape
import { useIsPortrait } from "@alessiofrittoli/react-hooks";
const isLandscape = !useIsPortrait();useIsTouchDevice
Detects if the current device supports touch events.
Type: boolean
trueif the device is touch device.falseotherwise.
import { useIsTouchDevice } from "@alessiofrittoli/react-hooks";
const isTouchDevice = useIsTouchDevice();useMediaQuery
Get Document Media matches and listen for changes.
| Parameter | Type | Default | Description |
| --------------------- | ------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| query | string | - | A string specifying the media query to parse into a MediaQueryList. |
| options | UseMediaQueryOptions\|UseMediaQueryStateOptions | - | An object defining custom options. |
| options.updateState | boolean | true | Indicates whether the hook will dispatch a React state update when the given query change event get dispatched. |
| options.onChange | OnChangeHandler | - | A custom callback that will be invoked on initial page load and when the given query change event get dispatched. |
| | | | This callback is required if updateState is set to false. |
Type: boolean|void
trueorfalseif the document currently matches the media query list or not.voidifupdateStateis set tofalse.
Check if user device prefers dark color scheme
import { useMediaQuery } from "@alessiofrittoli/react-hooks";
const isDarkOS = useMediaQuery("(prefers-color-scheme: dark)");Listen changes with no state updates
import { useMediaQuery } from "@alessiofrittoli/react-hooks";
useMediaQuery("(prefers-color-scheme: dark)", {
updateState: false,
onChange(matches) {
console.log("is dark OS?", matches);
},
});usePreventContextMenu
Prevents the context menu from appearing on a specified target element.
| Parameter | Type | Default | Description |
| --------- | ----------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------- |
| target | EventListenerTarget\|React.RefObject<EventListenerTarget> | window | The target element or a React.RefObject where the context menu should be prevented. |
| | | | If not provided, the listener will be attached to the top window. |
// Prevent context menu on the entire top window.
usePreventContextMenu();
// Prevent context menu on a specific target.
const ref = useRef<HTMLDivElement>(null);
usePreventContextMenu(ref);useDocumentVisibility
Track the visibility state of the document (i.e., whether the page is visible or hidden).
| Parameter | Type | Default | Description |
| ---------------------------- | ------------------------------------------------------------------------- | ------- | --------------------------------------------------------------------- |
| options | UseDocumentVisibilityOptions\|StateDisabledUseDocumentVisibilityOptions | - | Configuration options for the hook. |
| options.updateState | boolean | true | Whether to update React state about Document visibility state or not. |
| options.onVisibilityChange | VisibilityChangeHandler | - | A custom callback executed when Document visiblity sate changes. |
Type: boolean | void
Returns true if the document is visible, false if hidden, or void if updateState is set to false.
Simple usage
import { useDocumentVisibility } from "@alessiofrittoli/react-hooks";
const isDocumentVisible = useDocumentVisibility();Disable state updates and listen visibility changes
import {
useDocumentVisibility,
type VisibilityChangeHandler,
} from "@alessiofrittoli/react-hooks";
const onVisibilityChange = useCallback<VisibilityChangeHandler>((isVisible) => {
// ... do something
}, []);
useDocumentVisibility({ updateState: false, onVisibilityChange });useWakeLock
Easily manage the Screen Wake Lock API to prevent the device screen from dimming or locking while your app is in use.
| Parameter | Type | Default | Description |
| ----------------- | ------------------------ | ------- | ---------------------------------------------------------------- |
| options | UseWakeLockOptions | - | (Optional) An object defining hook options. |
| options.onMount | boolean | true | Indicates whether to request the screen WakeLock on mount. |
| options.onError | OnWakeLockRequestError | - | A custom callback executed when a screen WakeLock request fails. |
Type: UseWakeLock
An object returning The current WakeLockSentinel instance or null if not enabled and utility functions.
wakeLock:WakeLockSentinel | null— The current Wake Lock instance, or null if not enabled.enabled:boolean— Whether the Wake Lock is currently active.requestWakeLock:() => Promise<void>— Manually request the Wake Lock.releaseWakeLock:() => Promise<void>— Manually release the Wake Lock.
Enable Wake Lock on mount
import { useWakeLock } from "@alessiofrittoli/react-hooks";
useWakeLock();Manually enable and disable Wake Lock
import { useWakeLock } from "@alessiofrittoli/react-hooks";
export const WakeLockButton: React.FC = () => {
const { enabled, requestWakeLock, releaseWakeLock } = useWakeLock({
enableOnLoad: false,
});
return (
<>
<h1>Wakelock enabled: {enabled.toString()}</h1>
<button onClick={requestWakeLock}>Enable wakelock</button>
<button onClick={releaseWakeLock}>Disable wakelock</button>
</>
);
};Handling Wake Lock errors
import {
useWakeLock,
type OnWakeLockRequestError,
} from "@alessiofrittoli/react-hooks";
const onError: OnWakeLockRequestError = (error) => {
alert("Could not enable Wake Lock: " + error.message);
};
export const WakeLockWithError: React.FC = () => {
const { enabled, requestWakeLock } = useWakeLock({ onError });
return (
<button onClick={requestWakeLock}>
{enabled ? "Wake Lock enabled" : "Enable Wake Lock"}
</button>
);
};DOM API
useFocusTrap
Trap focus inside the given HTML Element.
This comes pretty handy when rendering a modal that shouldn't be closed without a user required action.
| Parameter | Type | Description |
| --------- | ------------------------------------ | ------------------------------------------------------------------------------------------- |
| target | React.RefObject<HTMLElement\|null> | The target HTMLElement React RefObject to trap focus within. |
| | | If no target is given, you must provide the target HTMLElement when calling setFocusTrap. |
Type: readonly [ SetFocusTrap, RestoreFocusTrap ]
A tuple containing:
setFocusTrap: A function to enable the focus trap. Optionally accept an HTMLElement as target.restoreFocusTrap: A function to restore the previous focus state.
Defining the target on hook initialization
import { useFocusTrap } from "@alessiofrittoli/react-hooks";
const modalRef = useRef<HTMLDivElement>(null);
const [setFocusTrap, restoreFocusTrap] = useFocusTrap(modalRef);
const modalOpenHandler = useCallback(() => {
if (!modalRef.current) return;
// ... open modal
setFocusTrap();
modalRef.current.focus(); // focus the dialog so next tab will focus the next element inside the modal
}, [setFocusTrap]);
const modalCloseHandler = useCallback(() => {
// ... close modal
restoreFocusTrap(); // cancel focus trap and restore focus to the last active element before enablig the focus trap
}, [restoreFocusTrap]);Defining the target ondemand
import { useFocusTrap } from "@alessiofrittoli/react-hooks";
const modalRef = useRef<HTMLDivElement>(null);
const modal2Ref = useRef<HTMLDivElement>(null);
const [setFocusTrap, restoreFocusTrap] = useFocusTrap();
const modalOpenHandler = useCallback(() => {
if (!modalRef.current) return;
// ... open modal
setFocusTrap(modalRef.current);
modalRef.current.focus();
}, [setFocusTrap]);
const modal2OpenHandler = useCallback(() => {
if (!modal2Ref.current) return;
// ... open modal
setFocusTrap(modal2Ref.current);
modal2Ref.current.focus();
}, [setFocusTrap]);useInView
Check if the given target Element is intersecting with an ancestor Element or with a top-level document's viewport.
| Parameter | Type | Description |
| --------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| target | React.RefObject<Element\|null> | The React.RefObject of the target Element to observe. |
| options | UseInViewOptions | (Optional) An object defining custom IntersectionObserver options. |
| options.root | Element\|Document\|false\|null | (Optional) Identifies the Element or Document whose bounds are treated as the bounding box of the viewport for the Element which is the observer's target. |
| options.margin | MarginType | (Optional) A string, formatted similarly to the CSS margin property's value, which contains offsets for one or more sides of the root's bounding box. |
| options.amount | 'all'\|'some'\|number\|number[] | (Optional) The intersecting target thresholds. |
| | | Threshold can be set to: |
| | | - all - 1 will be used. |
| | | - some - 0.5 will be used. |
| | | - number |
| | | - number[] |
| options.once | boolean | (Optional) By setting this to true the observer will be disconnected after the target Element enters the viewport. |
| options.initial | boolean | (Optional) Initial value. This value is used while server rendering then will be updated in the client based on target visibility. Default: false. |
| options.enable | boolean | (Optional) Defines the initial observation activity. Use the returned setEnabled to update this state. Default: true. |
| options.onIntersect | OnIntersectStateHandler | (Optional) A custom callback executed when target element's visibility has crossed one or more thresholds. |
| | | This callback is awaited before any state update. |
| | | If an error is thrown the React State update won't be fired. |
| | | ⚠️ Wrap your callback with useCallback to avoid unnecessary IntersectionObserver recreation. |
| options.onEnter | OnIntersectHandler | (Optional) A custom callback executed when target element's visibility has crossed one or more thresholds. |
| | | This callback is awaited before any state update. |
| | | If an error is thrown the React State update won't be fired. |
| | | ⚠️ Wrap your callback with useCallback to avoid unnecessary IntersectionObserver recreation. |
| options.onExit | OnIntersectHandler | (Optional) A custom callback executed when target element's visibility has crossed one or more thresholds. |
| | | This callback is awaited before any state update. |
| | | If an error is thrown the React State update won't be fired. |
| | | ⚠️ Wrap your callback with useCallback to avoid unnecessary IntersectionObserver recreation. |
Type: UseInViewReturnType
An object containing:
inView:boolean- Indicates whether the target Element is in viewport or not.setInView:React.Dispatch<React.SetStateAction<boolean>>- A React Dispatch SetState action that allows custom state updates.enabled:boolean- Indicates whether the target Element is being observed or not.setEnabled:React.Dispatch<React.SetStateAction<boolean>>- A React Dispatch SetState action that allows to enable/disable observation when needed.observer:IntersectionObserver | undefined- TheIntersectionObserverinstance. It could beundefinedifIntersectionObserveris not available or observation is not enabled.
Basic usage
"use client";
import { useRef } from "react";
import { useInView } from "@alessiofrittoli/react-hooks";
const UseInViewExample: React.FC = () => {
const targetRef = useRef<HTMLDivElement>(null);
const { inView } = useInView(ref);
return Array.from(Array(6)).map((value, index) => (
<div
key={index}
style={{
height: "50vh",
border: "1px solid red",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
ref={index === 2 ? targetRef : undefined}
style={{
width: 150,
height: 150,
borderRadius: 12,
display: "flex",
alignItems: "center",
justifyContent: "center",
background: inView ? "#51AF83" : "#201A1B",
color: inView ? "#201A1B" : "#FFFFFF",
}}
>
{index + 1}
</div>
</div>
));
};Disconnect observer after target enters the viewport
"use client";
import { useRef } from "react";
import { useInView } from "@alessiofrittoli/react-hooks";
const OnceExample: React.FC = () => {
const targetRef = useRef<HTMLDivElement>(null);
const { inView } = useInView(targetRef, { once: true });
useEffect(() => {
if (!inView) return;
console.count("Fired only once: element entered viewport.");
}, [inView]);
return (
<div
ref={targetRef}
style={{
height: 200,
background: inView ? "lime" : "gray",
}}
/>
);
};Observe target only when needed
"use client";
import { useRef } from "react";
import { useInView } from "@alessiofrittoli/react-hooks";
const OnDemandObservation: React.FC = () => {
const targetRef = useRef<HTMLDivElement>(null);
const { inView, enabled, setEnabled } = useInView(targetRef, {
enable: false,
});
return (
<div>
<button onClick={() => setEnabled((prev) => !prev)}>
{enabled ? "Disconnect observer" : "Observe"}
</button>
<div
ref={targetRef}
style={{
height: 200,
marginTop: 50,
background: inView ? "lime" : "gray",
}}
/>
</div>
);
};Execute custom callback when intersection occurs
"use client";
import { useRef } from "react";
import {
useInView,
type OnIntersectStateHandler,
} from "@alessiofrittoli/react-hooks";
const AsyncStartExample: React.FC = () => {
const targetRef = useRef<HTMLDivElement>(null);
const onIntersect = useCallback<OnIntersectStateHandler>(
async ({ entry, isEntering }) => {
if (isEntering) {
console.log("Delaying state update...");
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate delay
console.log("Async task completed. `inView` will now be updated.");
return;
}
console.log("Delaying state update...");
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate delay
console.log("Async task completed. `inView` will now be updated.");
},
[],
);
const { inView } = useInView(targetRef, { onIntersect });
return (
<div
ref={targetRef}
style={{
height: 200,
background: inView ? "lime" : "gray",
}}
/>
);
};Execute custom callback when onEnter and onExit
"use client";
import { useRef } from "react";
import {
useInView,
type OnIntersectHandler,
} from "@alessiofrittoli/react-hooks";
const AsyncStartExample: React.FC = () => {
const targetRef = useRef<HTMLDivElement>(null);
const onEnter = useCallback<OnIntersectHandler>(async ({ entry }) => {
console.log("In viewport - ", entry);
}, []);
const onExit = useCallback<OnIntersectHandler>(async ({ entry }) => {
console.log("Exited viewport - ", entry);
}, []);
const { inView } = useInView(targetRef, { onEnter, onExit });
return (
<div
ref={targetRef}
style={{
height: 200,
background: inView ? "lime" : "gray",
}}
/>
);
};useScrollBlock
Prevent Element overflow.
| Parameter | Type | Default | Description |
| --------- | ------------------------------------ | -------------------------- | -------------------------------------------------- |
| target | React.RefObject<HTMLElement\|null> | Document.documentElement | (Optional) The React RefObject target HTMLElement. |
Type: [ () => void, () => void ]
A tuple with block and restore scroll callbacks.
Block Document Overflow
import { useScrollBlock } from '@alessiofrittoli/react-hooks'
const [ blockScroll, restoreScroll ] = useScrollBlock()
const openPopUpHandler = useCallback( () => {
...
blockScroll()
}, [ blockScroll ] )
const closePopUpHandler = useCallback( () => {
...
restoreScroll()
}, [ restoreScroll ] )
...Block HTML Element Overflow
const elementRef = useRef<HTMLDivElement>( null )
const [ blockScroll, restoreScroll ] = useScrollBlock( elementRef )
const scrollBlockHandler = useCallback( () => {
...
blockScroll()
}, [ blockScroll ] )
const scrollRestoreHandler = useCallback( () => {
...
restoreScroll()
}, [ restoreScroll ] )
...Miscellaneous
useInput
Handle input states with ease.
| Parameter | Description |
| --------- | ---------------------- |
| I | The input value type. |
| O | The output value type. |
| Parameter | Type | Default | Description |
| ---------------------- | ---------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| options | UseInputOptions<I, O> | {} | An object defining custom options. |
| options.inputRef | React.RefObject<InputType> | - | (Optional) The React HTML input element ref. |
| options.initialValue | O\|null | - | (Optional) The input initial value. |
| options.touchTimeout | number | 600 | (Optional) A timeout in milliseconds which will be used to define the input as "touched" thus validations are triggered and errors can be displayed. |
| options.validate | ValidateValueHandler<O> | - | (Optional) Value validation handler. If parse callback is given, the value will be parsed before validation. |
| options.parse | ParseValueHandler<I, O> | - | (Optional) Parse value. |
| options.onChange | ChangeHandler<O> | - | (Optional) A callable function executed when the ChangeEvent is dispatched on the HTML input element. |
Type: UseInputOutput<I, O>
An object containing the following properties:
| Property | Type | Description |
| --------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| isEmpty | boolean | Indicates whether the Input is empty or not. |
| hasError | boolean | Indicates whether the input has error or not. |
| | | It will return true if the Input does not pass the validation checks and it has been touched. |
| | | Please refer to the isValid property to check the Input validity regardless of whether it has been touched or not. |
| changeHandler | React.ChangeEventHandler<InputType> | Change handler callback used to handle Input change events. |
| blurHandler | () => void | Blur handler callback used to handle Input blur events. |
| setValue | ( value: O ) => void | Call setValue method to update input value. |
| submit | () => void | Call submit method to re-run validations and ensure error state is updated successfully. |
| reset | () => void | Call reset method to reset the Input state. |
| focus | () => void | Call focus method to focus the Input Element. inputRef must be provided in the input options. |
Basic usage
const MyComponent: React.FC = () => {
const input = useInput<string>();
return (
<input
type="text"
value={input.value || ""}
onChange={input.changeHandler}
onBlur={input.blurHandler}
/>
);
};Displaying custom error messages
import {
useInput,
type ValidateValueHandler,
} from "@alessiofrittoli/react-hooks";
const isNotEmpty: ValidateValueHandler<string> = (value) =>
!value ? false : value.trim().length > 0;
const MyComponent: React.FC = () => {
const input = useInput<string>({
validate: isNotEmpty,
});
return (
<>
<input
value={input.value || ""}
onChange={input.changeHandler}
onBlur={input.blurHandler}
/>
{input.hasError && <span>The input cannot be empty.</span>}
</>
);
};Parsing and validating parsed value
import { formatDate, isValidDate } from "@alessiofrittoli/date-utils";
import {
useInput,
type ValidateValueHandler,
type ParseValueHandler,
} from "@alessiofrittoli/react-hooks";
const parseStringToDate: ParseValueHandler<string, Date> = (value) =>
value ? new Date(value) : undefined;
const validateInputDate: ValidateValueHandler<Date> = (value) =>
isValidDate(value) && value.getTime() > Date.now();
const MyComponent: React.FC = () => {
const input = useInput<string, Date>({
parse: parseStringToDate,
validate: validateInputDate,
});
return (
<>
<input
type="datetime-local"
value={input.value ? formatDate(input.value, "Y-m-dTH:i") : ""}
onChange={input.changeHandler}
onBlur={input.blurHandler}
/>
{input.hasError && (
<span>Please choose a date no earlier than today</span>
)}
</>
);
};useDeferCallback
useDeferCallback will return a memoized and deferred version of the callback that only changes if one of the inputs in the dependency list has changed.
Since deferCallback returns a new function when called, it may cause your child components to uselessly re-validate when a state update occurs in the main component.
To avoid these pitfalls you can memoize and defer your task with useDeferCallback.
Take a look at deferTask to defer single tasks in a function handler.
| Parameter | Description |
| --------- | ------------------------------------------------------------------------------------------------- |
| T | The task function definition. unknown types will be inherited by your function type definition. |
| U | The task function arguments. unknown types will be inherited by your function type. |
| Parameter | Type | Description |
| --------- | ---- | --------------------------- |
| task | T | The task callable function. |
Type: ( ...args: U ) => Promise<Awaited<ReturnType<T>>>
A new memoized handler which returns a new Promise that returns the task result once fulfilled.
const MyComponent: React.FC = () => {
const clickHandler = useDeferCallback<React.MouseEventHandler>(
event => { ... }, []
)
return (
<button onClick={ clickHandler }>Button</button>
)
}useEffectOnce
Modified version of useEffect that only run once on intial load.
| Parameter | Type | Description |
| --------- | ---------------------- | ------------------------------------------------------- |
| effect | React.EffectCallback | Imperative function that can return a cleanup function. |
"use client";
import { useEffect, useState } from "react";
import { useEffectOnce } from "@alessiofrittoli/react-hooks";
export const ClientComponent: React.FC = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const intv = setInterval(() => {
setCount((prev) => prev + 1); // update state each 1s
}, 1000);
return () => clearInterval(intv);
}, []);
useEffectOnce(() => {
console.log("Component did mount");
return () => {
console.log("Component did unmount");
};
});
return <div>{count}</div>;
};useUpdateEffect
Modified version of useEffect that skips the first render.
| Parameter | Type | Description |
| --------- | ---------------------- | ----------------------------------------------------------------------- |
| effect | React.EffectCallback | Imperative function that can return a cleanup function. |
| deps | React.DependencyList | If present, effect will only activate if the values in the list change. |
"use client";
import { useEffect, useState } from "react";
import { useUpdateEffect } from "@alessiofrittoli/react-hooks";
export const ClientComponent: React.FC = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const intv = setInterval(() => {
setCount((prev) => prev + 1);
}, 1000);
return () => clearInterval(intv);
}, []);
useEffect(() => {
console.log("useEffect", count); // starts from 0
return () => {
console.log("useEffect - clean up", count); // starts from 0
};
}, [count]);
useUpdateEffect(() => {
console.log("useUpdateEffect", count); // starts from 1
return () => {
console.log("useUpdateEffect - clean up", count); // starts from 1
};
}, [count]);
return <div>{count}</div>;
};useIsClient
Check if the React Hook or Component where this hook is executed is running in a browser environmen
