# Progettare un custom hook

Un custom hook è una funzione che inizia con use e restituisce solo ciò che serve: comportamento, non markup, con una firma che regge la crescita.

> Pagina: https://ui.eneawork.it/blog/react-custom-hooks-design
> Markdown: https://ui.eneawork.it/blog/react-custom-hooks-design.md

*2026-11-01*

Un custom hook è una funzione che inizia con use, chiama altri hook e restituisce solo ciò che il chiamante usa: se il nome descrive un comportamento e la firma non espone i dettagli interni, l’hook è progettato bene.

Il catalogo EneUI conta 111.985 componenti, 1.556 blocchi e 12.808 icone da 1.472 librerie, misurati da /api/stats il 1 novembre 2026. La cifra viene da una query all’endpoint, non da una stima: conta i record pubblicati e non dice quanti hook del catalogo rispettino questa disciplina.

## Comportamento, non interfaccia

Un hook non disegna niente: legge, calcola, si iscrive a eventi e restituisce dati e azioni. Il markup resta nel componente, che è l’unico punto in cui si decide dove va un bottone e quale testo mostra.

## Una firma che regge

import { useEffect, useState } from "react"; export function useDebouncedValue(value: T, delay = 300) { const [debounced, setDebounced] = useState(value); useEffect(() => { const id = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(id); }, [value, delay]); return debounced; }

Tre scelte rendono la firma stabile: un solo argomento di ingresso, un ritardo con valore predefinito, un solo valore di ritorno. Il chiamante non può impostare lo stato interno, quindi il comportamento resta uno e testabile.

## Cosa restituire

## Gli errori che si pagano

## Testare l’hook da solo

import { renderHook, act } from "@testing-library/react"; import { useDebouncedValue } from "./use-debounced-value"; test("returns the last value after the delay", () => { vi.useFakeTimers(); const { result, rerender } = renderHook(({ v }) => useDebouncedValue(v), { initialProps: { v: "a" }, }); rerender({ v: "ab" }); act(() => vi.advanceTimersByTime(300)); expect(result.current).toBe("ab"); });

renderHook monta il hook senza markup: il test misura il comportamento, non il DOM. Serve act per far girare i timer dentro il ciclo di React, altrimenti l’asserzione arriva prima dell’aggiornamento.

## Il comando

npm install --save-dev vitest @testing-library/react jsdom npx vitest run

Vitest e Testing Library sono distribuiti con licenza MIT; il test gira in jsdom, che simula il DOM senza aprire un browser.

## Record vivi nel catalogo

Tre record mostrano la stessa idea: use-image-upload incapsula caricamento e anteprima, multi-step-form tiene il passo corrente fuori dai singoli campi, e la categoria input raccoglie i campi che quegli hook comandano.

## La limitazione

Un hook personalizzato non condivide lo stato fra due chiamanti: ogni componente ottiene la sua istanza. Per uno stato condiviso serve un context o uno store esterno; l’estrazione riduce il codice duplicato, non sostituisce un gestore di stato.

## Dove continuare

Le regole che valgono anche qui sono in regole degli hook; cosa può restare derivato invece che nello stato è in stato derivato; la stessa idea in Vue è nei composables; il confronto fra i framework è in Vue 3 vs React.

## FAQ

### Che differenza c’è fra un custom hook e una funzione normale?

Un custom hook può chiamare altri hook, quindi usare stato ed effetti; una funzione normale no. Il prefisso use lo dice al linter e a chi legge.

### Un custom hook deve restituire uno stato?

No. Può restituire un valore calcolato, una funzione o un oggetto con dati e azioni: conta che la firma descriva il comportamento.

### Quanti argomenti può avere un custom hook?

Tecnicamente quanti vuoi, in pratica oltre tre conviene un oggetto di opzioni: il call site resta leggibile e aggiungere un campo non rompe l’ordine.

### Posso testare un custom hook senza browser?

Sì, con renderHook di Testing Library e un ambiente jsdom. Il test controlla il valore restituito e le funzioni esposte, non il markup.

### Perché non restituire JSX da un hook?

Perché il hook diventerebbe un componente con un nome che inganna: React non lo monta come tale e il test deve ricostruire l’interfaccia.
