npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@alessiofrittoli/react-hooks

v3.7.1

Published

TypeScript React utility Hooks

Readme

React Hooks 🪝

NPM Latest Version Coverage Status Socket Status NPM Monthly Downloads Dependencies

GitHub Sponsor

TypeScript React utility Hooks

Table of Contents


Getting started

Run the following command to start using react-hooks in your projects:

npm i @alessiofrittoli/react-hooks

or using pnpm

pnpm i @alessiofrittoli/react-hooks

ESLint 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 🎉


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: UseConnectionReturnType

An object with the following properties:

  • connection: online | offline - Indicates the connections status.
  • isOnline: boolean - Indicates whether the current device is online.
  • isOffline: boolean - Indicates whether the current device is offline.

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 localStorage under the key dark-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. | | 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. | | 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. | | 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. | | options | CustomEventListenerOptions<T, K> | An object defining init options. | | options.target | Document\|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>
  )

}


useIsPortrait

Check if device is portrait oriented.

React State get updated when device orientation changes.

Type: boolean

  • true if the device is portrait oriented.
  • false otherwise.

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

  • true if the device is touch device.
  • false otherwise.

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

  • true or false if the document currently matches the media query list or not.
  • void if updateState is set to false.

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);
  },
});

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 - The IntersectionObserver instance. It could be undefined if IntersectionObserver is 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 environment.

This is pretty usefull to avoid hydration errors.

Type: boolean

  • true if the React Hook or Component is running in a browser environment.
  • false otherwise.

"use client";

import { useIsClient } from "@alessiofrittoli/react-hooks";

export const ClientComponent: React.FC = () => {
  const isClient = useIsClient();

  return <div>Running {!isClient ? "server" : "client"}-side</div>;
};

useIsFirstRender

Check if is first React Hook/Component render.

Type: boolean

  • true at the mount time.
  • false otherwise.

Note that if the React Hook/Component has no state updates, useIsFirstRender will always return true.


"use client";

import { useIsFirstRender } from "@alessiofrittoli/react-hooks";

export const ClientComponent: React.FC = () => {
  const isFirstRender = useIsFirstRender();
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const intv = setInterval(() => {
      setCounter((prev) => prev + 1);
    }, 1000);
    return () => clearInterval(intv);
  }, []);

  return (
    <div>
      {isFirstRender ? "First render" : "Subsequent render"}
      <hr />
      {counter}
    </div>
  );
};

usePagination

Get pagination informations based on the given options.

This hook memoize the returned result of the paginate function imported from @alessiofrittoli/math-utils.

See paginate function Documentation for more information about it.


useSelection

A React hook for managing selection states in an array.

Provides functionality for single and group selection, as well as resetting the selection.

| Parameter | Description | | --------- | -------------------------------------- | | V | The type of the values in the array. |


| Parameter | Type | Default | Description | | --------- | ----- | ------- | ------------------------------------------- | | array | V[] | - | The array of items to manage selection for. | | initial | V[] | [] | The initial selection state. |


An object containing the selection state and handlers.

  • selection: V[] - The current selected items.
  • hasSelection: boolean - Indicates whether selection is not empty. Short-hand for selection.length > 0.
  • isSelected: IsSelectedHandler<V> - Check if the given entry is in the selection.
  • setSelection: SetSelectionHandler<V> - A React Dispatch SetStateAction that allows custom selection update.
  • select: SelectHandler<V> - Update selection by adding a new entry or removing the given entry if already exists in the selection.
  • groupSelect: GroupSelectHandler<V> - Select all items from the given array starting from the first item in the selection up to the given entry.
  • selectAll: SelectAllHandler - Add all entries from the given array to the selection.
  • resetSelection: ResetSelectionHandler - Removes all entries from the selection.

"use client";

import { useCallback, useMemo } from "react";
import { useSelection } from "@alessiofrittoli/react-hooks";

interface Item {
  id: number;
  name: string;
}

const items: Item[] = [
  {
    id: 1,
    name: "item-1",
  },
  {
    id: 2,
    name: "item-2",
  },
  {
    id: 3,
    name: "item-3",
  },
  {
    id: 4,
    name: "item-4",
  },
  {
    id: 5,
    name: "item-5",
  },
];

const MyComponent: React.FC = () => {
  const { setSelection, select, groupSelect, isSelected } = useSelection(
    useMemo(() => items.map((item) => item.id), [])
  );

  const clickHandler = useCallback(
    (id: Item["id"]) => (event: React.MouseEvent<HTMLButtonElement>) => {
      if (event.shiftKey) {
        return groupSelect(id); // group select
      }
      if (event.metaKey || event.ctrlKey) {
        return select(id); // toggle single item in selection
      }
      setSelection(
        (prev) => (prev.includes(id) ? [] : [id]) // toggle single item selection
      );
    },
    [select, groupSelect, setSelection]
  );

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          <button
            onClick={clickHandler(item.id)}
            style={{
              border: isSelected(item.id)
                ? "1px solid red"
                : " 1px solid black",
            }}
          >
            {item.name}
          </button>
        </li>
      ))}
    </ul>
  );
};

Timers

useDebounce

Debounce a value by a specified delay.

This hook returns a debounced version of the input value, which only updates after the specified delay has passed without any changes to the input value.

It is useful for scenarios like search input fields or other cases where frequent updates should be minimized.

The Timeout automaticall