Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useState, useEffect, useCallback } from "react";
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void, () => void] {
// O inicializador preguiçoso lê do armazenamento apenas uma vez
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === "undefined") return initialValue;
try {
const item = localStorage.getItem(key);
return item !== null ? (JSON.parse(item) as T) : initialValue;
} catch {
return initialValue;
}
});
// Persiste no localStorage sempre que o valor ou a chave mudam
useEffect(() => {
if (typeof window === "undefined") return;
try {
localStorage.setItem(key, JSON.stringify(storedValue));
} catch {
// Cota de armazenamento excedida ou indisponível
}
}, [key, storedValue]);
// Sincroniza entre abas via evento de armazenamento
useEffect(() => {
if (typeof window === "undefined") return;
const handleStorage = (e: StorageEvent) => {
if (e.key !== key) return;
if (e.newValue === null) {
setStoredValue(initialValue);
} else {
try {
setStoredValue(JSON.parse(e.newValue) as T);
} catch {
// Ignora JSON malformado
}
}
};
window.addEventListener("storage", handleStorage);
return () => window.removeEventListener("storage", handleStorage);
}, [key, initialValue]);
// Setter que corresponde à assinatura do useState (valor ou função atualizadora)
const setValue = useCallback(
(value: T | ((prev: T) => T)) => {
setStoredValue((prev) => {
const nextValue =
value instanceof Function ? value(prev) : value;
return nextValue;
});
},
[]
);
// Remove a chave do armazenamento e redefine para o valor inicial
const remove = useCallback(() => {
if (typeof window !== "undefined") {
localStorage.removeItem(key);
}
setStoredValue(initialValue);
}, [key, initialValue]);
return [storedValue, setValue, remove];
}Quando usar isso: Você quer que o estado do componente sobreviva a atualizações de página e, opcionalmente, permaneça sincronizado entre abas do navegador, sem precisar de uma biblioteca externa.
"use client";
function ThemeToggle() {
const [theme, setTheme, removeTheme] = useLocalStorage<"light" | "dark">(
"app-theme",
"light"
);
return (
<div>
<p>Tema atual: {theme}</p>
<button onClick={() => setTheme((t) => (t === "light" ? "dark" : "light"))}>
Alternar Tema
</button>
<button onClick={removeTheme}>Redefinir para Padrão</button>
</div>
);
}
function FormDraft() {
const [draft, setDraft] = useLocalStorage("form-draft", {
name: "",
email: "",
});
return (
<form>
<input
value={draft.name}
onChange={(e) => setDraft((d) => ({ ...d, name: e.target.value }))}
placeholder="Nome"
/>
<input
value={draft.email}
onChange={(e) => setDraft((d) => ({ ...d, email: e.target.value }))}
placeholder="Email"
/>
<p>Rascunho salvo automaticamente no localStorage</p>
</form>
);
}O que isso demonstra:
"light" | "dark"(prev) => next, assim como useStateremove para limpar a chave e redefinir o estadostorageuseState lê do localStorage apenas na primeira renderização, evitando leituras redundantes a cada re-renderização.window é controlado por verificações typeof window === "undefined", então o hook retorna initialValue durante a renderização do lado do servidor sem inconsistência de hidratação.JSON.stringify e desserializados com JSON.parse. Isso lida com tipos primitivos, arrays e objetos simples.storage dispara em outras abas quando a mesma chave muda. O listener atualiza o estado local para corresponder.(prev) => next, espelhando a API do useState.| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
key | string | - | A chave do localStorage |
initialValue | T | - | Valor de fallback quando a chave está ausente ou em SSR |
| Índice de Retorno | Tipo | Descrição |
|---|---|---|
[0] | T | Valor atual |
[1] | (value: T or ((prev: T) => T)) => void | Setter (valor ou função atualizadora) |
[2] | () => void | Remove a chave e redefine para o valor inicial |
Com expiração: Adicione um TTL armazenando { value, expiresAt } e verificando na leitura:
const item = JSON.parse(raw);
if (item.expiresAt && Date.now() > item.expiresAt) {
localStorage.removeItem(key);
return initialValue;
}
return item.value;Com serializador personalizado: Aceite opções serialize e deserialize para dados não-JSON (por exemplo, superjson para objetos Date, Map, Set).
Variante sessionStorage: Troque localStorage por sessionStorage - a API é idêntica, mas os dados são limpos quando a aba é fechada.
T flui de initialValue para o estado armazenado e o setter, permitindo inferência de tipo completa."light" | "dark" restringem a entrada do setter automaticamente.useLocalStorage<User>("user", defaultUser).undefined e objetos circulares não sobrevivem ao JSON.stringify. Correção: Armazene apenas dados simples serializáveis. Use superjson para Date, Map, Set.storage. Correção: Este hook lida com atualizações na mesma aba via setState; a sincronização entre abas é tratada pelo listener de eventos.myapp:theme.initialValue mas o cliente lê um valor diferente do armazenamento, ocorre uma inconsistência. Correção: O inicializador preguiçoso é executado apenas no cliente. Para frameworks SSR, a renderização inicial usa initialValue, depois atualiza após a hidratação.| Pacote | Nome do Hook | Notas |
|---|---|---|
usehooks-ts | useLocalStorage | Popular, API similar |
@uidotdev/usehooks | useLocalStorage | Mínimo, bem testado |
ahooks | useLocalStorageState | Suporta serializador personalizado |
jotai | atomWithStorage | Persistência baseada em átomo |
zustand | persist middleware | Persistência em nível de store |
storage dispara em outras abas quando a mesma chave é escrita no localStorage.setState.O inicializador preguiçoso (forma de callback do useState) é executado apenas na primeira renderização. Ler o localStorage a cada renderização seria um desperdício, já que o valor armazenado só precisa ser lido uma vez no momento da montagem.
JSON.stringify.undefined também é descartado (torna-se null).Date, Map e Set perdem seus tipos (tornam-se strings/arrays). Use superjson para estes.Navegadores limitam o localStorage a cerca de 5 MB por origem. Quando o limite é atingido, setItem lança um erro. O hook envolve a escrita em um try/catch para que o aplicativo não trave, mas os dados não são persistidos silenciosamente.
Prefira suas chaves com um namespace específico do aplicativo:
const [theme, setTheme] = useLocalStorage("myapp:theme", "light");O servidor renderiza com initialValue, mas o cliente lê um valor diferente do armazenamento na montagem. O inicializador preguiçoso é executado apenas no cliente, causando uma breve inconsistência. Isso é esperado; para interfaces críticas, considere atrasar a renderização até que a hidratação seja concluída.
JSON.parse converte strings de Data de volta para strings simples, não objetos Date. Use um serializador personalizado como superjson que preserva tipos, ou reidrate manualmente as datas após a leitura.
O callback setValue verifica value instanceof Function. Se for verdadeiro, ele chama a função com o estado anterior. Caso contrário, ele usa o valor diretamente. Isso espelha a API do useState.
O genérico T é inferido de initialValue. Por exemplo, useLocalStorage("theme", "light") infere T como string. Para tipos de união, passe o genérico explicitamente:
useLocalStorage<"light" | "dark">("theme", "light");Sim. A API do sessionStorage é idêntica à do localStorage. Troque todas as chamadas de localStorage por sessionStorage. A única diferença é que os dados são limpos quando a aba do navegador é fechada.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥