# @julianobazzi/nextjs-utils
[English](README.md) | **Português**
Uma coleção de hooks e utilitários reutilizáveis para **Next.js (App Router)**, escritos em TypeScript.
**Feito para Next.js, compatível com React 18 e 19.** Os hooks client já vêm com a
diretiva `'use client'` aplicada, então você importa direto em Server Components sem
precisar de wrapper. `next`, `react` e `react-dom` são peer dependencies — a lib
sempre usa as cópias do seu próprio projeto.
## Instalação
```bash
pnpm add @julianobazzi/nextjs-utils
# ou
npm install @julianobazzi/nextjs-utils
# ou
yarn add @julianobazzi/nextjs-utils
```
Requer Next.js 14+ (App Router) com React 18 ou 19 já instalado no projeto.
## Hooks
| Hook | Descrição |
| --- | --- |
| `useToggle` | Estado booleano com toggle e setter explícito |
| `usePrevious` | O valor do render anterior |
| `useDebounce` | Cópia com debounce de um valor que muda rápido, ou um callback com debounce |
| `useThrottle` | Cópia com throttle de um valor que muda rápido, ou um callback com throttle |
| `useLocalStorage` | Estado persistido no `localStorage`, sincronizado entre abas |
| `useCookie` | Estado persistido em cookie, sincronizado entre componentes |
| `useMediaQuery` | Acompanha reativamente uma media query CSS (SSR-safe) |
| `useEventListener` | Anexa um event listener DOM/window com limpeza automática |
| `useInterval` | Executa um callback em intervalo (pausável com `null`) |
| `useOnlineStatus` | Acompanha o status de conexão do navegador (SSR-safe) |
| `usePageVisibility` | Acompanha se a aba está visível (SSR-safe) |
| `useIdle` | Detecta quando o usuário fica inativo por um tempo |
| `useBeforeUnload` | Confirmação antes de sair da página (alterações não salvas) |
| `useBroadcastChannel` | Envia/recebe mensagens entre abas da mesma origem |
| `useHistoryState` | Estado com histórico de undo/redo |
| `createCan` | Cria um par de controle de acesso por papel (`useCan` + `Can`) |
### Hooks Next.js (subpath `@julianobazzi/nextjs-utils/next`)
| Hook | Descrição |
| --- | --- |
| `useSearchParam` | Lê um query param tipado (`next/navigation`) |
| `useUpdateQueryParams` | Escreve vários search params da URL a partir de um objeto |
| `useUpdateSearchParam` | Escreve um único search param da URL |
| `useFilterQueryParams` | Sincroniza o estado de um form de filtro com a URL |
| `useActiveRoute` | Se a rota atual corresponde a um link (item de menu ativo) |
| `usePaginationParams` | Estado de paginação (`page`/`pageSize`) guardado na URL |
| `useSortParams` | Estado de ordenação de tabela guardado na URL |
| `useHashParam` | Lê/escreve o fragmento `#hash` da URL (SSR-safe) |
## Utilitários
| Utilitário | Descrição |
| --- | --- |
| `allowNumericKeyDown` | Handler `onKeyDown` que restringe um input a números |
## Uso
### `useToggle`
```tsx
import { useToggle } from '@julianobazzi/nextjs-utils';
const [isOpen, toggle, setOpen] = useToggle(false);
// toggle() -> inverte o valor
// setOpen(true) -> define um valor explícito
```
### `usePrevious`
```tsx
import { usePrevious } from '@julianobazzi/nextjs-utils';
const contagemAnterior = usePrevious(count);
```
### `useDebounce`
```tsx
import { useDebounce } from '@julianobazzi/nextjs-utils';
// Forma de valor — retorna o valor após ele parar de mudar
const buscaComDebounce = useDebounce(search, 300);
// Forma de função — retorna um callback com debounce e identidade estável
const salvarComDebounce = useDebounce((value: string) => save(value), 300);
salvarComDebounce('hello');
```
### `useLocalStorage`
```tsx
import { useLocalStorage } from '@julianobazzi/nextjs-utils';
const [theme, setTheme, removeTheme] = useLocalStorage('theme', 'light');
setTheme('dark');
setTheme((prev) => (prev === 'dark' ? 'light' : 'dark'));
```
### `useMediaQuery`
```tsx
import { useMediaQuery } from '@julianobazzi/nextjs-utils';
const isDesktop = useMediaQuery('(min-width: 1024px)');
```
### `useEventListener`
```tsx
import { useEventListener } from '@julianobazzi/nextjs-utils';
useEventListener('keydown', (event) => {
if (event.key === 'Escape') close();
});
```
### `useInterval`
```tsx
import { useInterval } from '@julianobazzi/nextjs-utils';
const [count, setCount] = useState(0);
const [rodando, setRodando] = useState(true);
// Passe `null` como delay para pausar o intervalo.
useInterval(() => setCount((c) => c + 1), rodando ? 1000 : null);
```
### `useThrottle`
```tsx
import { useThrottle } from '@julianobazzi/nextjs-utils';
// Forma de valor — atualiza no máximo uma vez por intervalo, terminando no valor mais recente
const scrollComThrottle = useThrottle(scrollY, 200);
// Forma de função — a primeira chamada roda na hora, as extras viram uma só no fim
const trackComThrottle = useThrottle((position: number) => track(position), 200);
```
### `useCookie`
Mesma API do `useLocalStorage`, mas persistindo em cookie (JSON serializado).
```tsx
import { useCookie } from '@julianobazzi/nextjs-utils';
const [consent, setConsent, removeConsent] = useCookie('consent', 'pending', { days: 365 });
setConsent('accepted');
```
### `useOnlineStatus` / `usePageVisibility`
```tsx
import { useOnlineStatus, usePageVisibility } from '@julianobazzi/nextjs-utils';
const isOnline = useOnlineStatus(); // false enquanto o navegador está offline
const isVisible = usePageVisibility(); // false enquanto a aba está em segundo plano
```
### `useIdle`
```tsx
import { useIdle } from '@julianobazzi/nextjs-utils';
const isIdle = useIdle(5 * 60 * 1000); // true após 5 minutos sem atividade
```
### `useBeforeUnload`
```tsx
import { useBeforeUnload } from '@julianobazzi/nextjs-utils';
useBeforeUnload(form.isDirty); // navegador confirma antes de fechar/recarregar
```
### `useBroadcastChannel`
```tsx
import { useBroadcastChannel } from '@julianobazzi/nextjs-utils';
const { postMessage } = useBroadcastChannel<'logout'>('auth', (message) => {
if (message === 'logout') signOut(); // disparado por outras abas
});
postMessage('logout'); // avisa todas as outras abas
```
### `useHistoryState`
```tsx
import { useHistoryState } from '@julianobazzi/nextjs-utils';
const { state, set, undo, redo, canUndo, canRedo } = useHistoryState('');
set('rascunho 1');
set('rascunho 2');
undo(); // -> 'rascunho 1'
redo(); // -> 'rascunho 2'
```
### `allowNumericKeyDown` (utilitário)
Handler `onKeyDown` que restringe um `` a digitação numérica. Teclas de
controle, navegação e atalhos passam sempre.
```tsx
import { allowNumericKeyDown } from '@julianobazzi/nextjs-utils';
;
allowNumericKeyDown(e, { allowDecimal: true })} />;
```
### `createCan` (controle de acesso por papel)
Desacoplado de qualquer provider de auth: passe um resolver que retorna o(s)
papel(éis) do usuário atual. Você recebe um hook `useCan` e um componente guard
`Can` que compartilham o mesmo resolver.
```tsx
import { createCan } from '@julianobazzi/nextjs-utils';
import { useAuth } from './auth';
// Conecte seu contexto de auth uma vez:
export const { useCan, Can } = createCan(() => useAuth().user?.role ?? null);
// Depois, em qualquer lugar:
const canEdit = useCan(['admin', 'editor']);