LocalStorage e SessionStorage
1. Introdução ao Armazenamento Local no Navegador
O armazenamento local no navegador é uma ferramenta essencial para aplicações web modernas. LocalStorage e SessionStorage fazem parte da Web Storage API e oferecem uma maneira simples de persistir dados no lado do cliente, sem necessidade de servidores ou cookies complexos.
Diferenças Fundamentais
| Característica | LocalStorage | SessionStorage |
|---|---|---|
| Persistência | Dados permanecem até serem removidos explicitamente | Dados são removidos ao fechar a aba/janela |
| Escopo | Todas as abas do mesmo domínio | Apenas na aba atual |
| Capacidade | ~5-10MB por domínio | ~5-10MB por aba |
Comparação com Cookies
Enquanto cookies são enviados automaticamente ao servidor em cada requisição HTTP, o Web Storage permanece exclusivamente no cliente. Use LocalStorage/SessionStorage para dados que não precisam ser enviados ao servidor, como preferências de usuário, estado de UI ou cache temporário. Cookies são mais adequados para autenticação e rastreamento entre sessões.
2. API Básica e Operações Essenciais
A API do Web Storage é surpreendentemente simples. Vamos explorar os métodos principais:
// Salvando dados
localStorage.setItem('usuario', 'João');
sessionStorage.setItem('tema', 'escuro');
// Recuperando dados
const usuario = localStorage.getItem('usuario'); // 'João'
const tema = sessionStorage.getItem('tema'); // 'escuro'
// Removendo um item específico
localStorage.removeItem('usuario');
// Limpando todo o storage
sessionStorage.clear();
Acessando como Objeto vs Métodos Oficiais
É possível acessar propriedades diretamente como objeto, mas isso não é recomendado:
// Funciona, mas não é recomendado
localStorage.minhaChave = 'valor';
const valor = localStorage.minhaChave; // 'valor'
// Problema: sobrescreve métodos nativos
localStorage.getItem = 'perigoso'; // Isso quebra a API!
Sempre prefira os métodos oficiais (setItem, getItem) para evitar conflitos e garantir compatibilidade.
Tratamento de Valores Ausentes
const dado = localStorage.getItem('chaveInexistente');
console.log(dado); // null (não undefined)
// Verificação segura
if (dado !== null) {
// Processar dado
}
3. Serialização e Desserialização de Dados Complexos
LocalStorage e SessionStorage só armazenam strings. Para objetos e arrays, use JSON:
// Salvando um objeto
const usuario = {
nome: 'Maria',
idade: 28,
preferencias: { tema: 'claro', notificacoes: true }
};
localStorage.setItem('usuario', JSON.stringify(usuario));
// Recuperando e convertendo de volta
const dadosBrutos = localStorage.getItem('usuario');
let usuarioRecuperado = null;
try {
usuarioRecuperado = JSON.parse(dadosBrutos);
console.log(usuarioRecuperado.nome); // 'Maria'
} catch (erro) {
console.error('Erro ao fazer parsing do JSON:', erro);
usuarioRecuperado = {}; // Fallback seguro
}
Tratamento de Erros com Try/Catch
Sempre envolva JSON.parse() em um bloco try/catch, pois dados corrompidos ou mal formatados podem lançar exceções:
function salvarDados(chave, dados) {
try {
localStorage.setItem(chave, JSON.stringify(dados));
} catch (erro) {
console.error('Erro ao salvar dados:', erro);
// Pode ser estouro de quota (QuotaExceededError)
}
}
function carregarDados(chave, valorPadrao = null) {
try {
const dados = localStorage.getItem(chave);
return dados ? JSON.parse(dados) : valorPadrao;
} catch (erro) {
console.error('Erro ao carregar dados:', erro);
return valorPadrao;
}
}
4. Gerenciamento de Estado com LocalStorage em React
Sincronizar estado React com LocalStorage é um padrão comum para persistência:
import { useState, useEffect } from 'react';
function ComponentePersistente() {
const [dados, setDados] = useState(() => {
// Inicialização preguiçosa (lazy initialization)
const salvos = localStorage.getItem('meusDados');
return salvos ? JSON.parse(salvos) : [];
});
// Sincronizar com LocalStorage sempre que 'dados' mudar
useEffect(() => {
localStorage.setItem('meusDados', JSON.stringify(dados));
}, [dados]);
return (
<div>
{/* Renderizar componente */}
</div>
);
}
Hook Customizado useLocalStorage
Crie um hook reutilizável para evitar repetição de código:
import { useState, useEffect, useCallback } from 'react';
function useLocalStorage(chave, valorInicial) {
const [valor, setValor] = useState(() => {
try {
const item = localStorage.getItem(chave);
return item ? JSON.parse(item) : valorInicial;
} catch (erro) {
console.error(`Erro ao ler ${chave}:`, erro);
return valorInicial;
}
});
const setValorPersistente = useCallback((novoValor) => {
setValor(novoValor);
try {
localStorage.setItem(chave, JSON.stringify(novoValor));
} catch (erro) {
console.error(`Erro ao salvar ${chave}:`, erro);
}
}, [chave]);
return [valor, setValorPersistente];
}
// Uso no componente
function Configuracoes() {
const [tema, setTema] = useLocalStorage('tema', 'claro');
const [idioma, setIdioma] = useLocalStorage('idioma', 'pt-BR');
return (
<div className={`app ${tema}`}>
<select value={idioma} onChange={(e) => setIdioma(e.target.value)}>
<option value="pt-BR">Português</option>
<option value="en-US">English</option>
</select>
<button onClick={() => setTema(tema === 'claro' ? 'escuro' : 'claro')}>
Alternar Tema
</button>
</div>
);
}
Estratégias para Evitar Perda de Estado
- Inicialização preguiçosa no
useState(como mostrado acima) - Debounce em operações frequentes de escrita
- Validação dos dados recuperados antes de usar
function useLocalStorageComValidacao(chave, valorInicial, validador) {
const [valor, setValor] = useState(() => {
try {
const item = localStorage.getItem(chave);
if (item) {
const parsed = JSON.parse(item);
return validador(parsed) ? parsed : valorInicial;
}
return valorInicial;
} catch {
return valorInicial;
}
});
// ... resto do hook
}
5. SessionStorage: Casos de Uso Práticos
SessionStorage é ideal para dados temporários que não devem sobreviver ao fechamento da aba:
// Fluxo de checkout em múltiplas etapas
function CheckoutStep1() {
const [dadosPedido, setDadosPedido] = useState(() => {
const salvos = sessionStorage.getItem('checkout');
return salvos ? JSON.parse(salvos) : {};
});
const avancarEtapa = (novosDados) => {
const atualizados = { ...dadosPedido, ...novosDados };
setDadosPedido(atualizados);
sessionStorage.setItem('checkout', JSON.stringify(atualizados));
// Navegar para próxima etapa
};
return (
<form onSubmit={(e) => {
e.preventDefault();
avancarEtapa({ etapa: 2, /* dados do formulário */ });
}}>
{/* Campos do formulário */}
</form>
);
}
Preferências de Sessão
function useSessionStorage(chave, valorInicial) {
const [valor, setValor] = useState(() => {
const item = sessionStorage.getItem(chave);
return item ? JSON.parse(item) : valorInicial;
});
useEffect(() => {
sessionStorage.setItem(chave, JSON.stringify(valor));
}, [chave, valor]);
return [valor, setValor];
}
function App() {
const [temaSessao, setTemaSessao] = useSessionStorage('tema-sessao', 'claro');
// Tema é resetado ao fechar a aba
}
6. Boas Práticas e Segurança
Nunca Armazenar Dados Sensíveis
// ❌ PERIGOSO: Nunca faça isso!
localStorage.setItem('token', 'meu-token-secreto');
localStorage.setItem('senha', 'minha-senha-123');
// ✅ Seguro: Armazene apenas dados não sensíveis
localStorage.setItem('preferenciaIdioma', 'pt-BR');
localStorage.setItem('ultimaPagina', '/dashboard');
Por quê? Dados no LocalStorage são acessíveis via JavaScript, tornando-os vulneráveis a ataques XSS.
Gerenciamento de Espaço
function verificarEspacoDisponivel() {
const tamanhoMaximo = 5 * 1024 * 1024; // ~5MB
let tamanhoUsado = 0;
for (let i = 0; i < localStorage.length; i++) {
const chave = localStorage.key(i);
const valor = localStorage.getItem(chave);
tamanhoUsado += (chave.length + valor.length) * 2; // UTF-16
}
const disponivel = tamanhoMaximo - tamanhoUsado;
console.log(`Espaço usado: ${tamanhoUsado / 1024}KB`);
console.log(`Espaço disponível: ${disponivel / 1024}KB`);
return disponivel > 0;
}
function limparDadosObsoletos() {
const versaoAtual = 2;
const versaoStorage = localStorage.getItem('versaoApp');
if (versaoStorage !== String(versaoAtual)) {
// Limpar dados antigos
localStorage.removeItem('cacheAntigo');
localStorage.setItem('versaoApp', versaoAtual);
console.log('Cache atualizado para nova versão');
}
}
Lidando com Ausência do Storage
Alguns navegadores em modo anônimo ou restrito podem bloquear o Web Storage:
function storageDisponivel(tipo = 'localStorage') {
try {
const storage = window[tipo];
const chaveTeste = '__teste__';
storage.setItem(chaveTeste, 'teste');
storage.removeItem(chaveTeste);
return true;
} catch (erro) {
console.warn(`${tipo} não disponível:`, erro.message);
return false;
}
}
// Fallback para memória
class MemoryStorage {
constructor() {
this.dados = new Map();
}
getItem(chave) { return this.dados.get(chave) || null; }
setItem(chave, valor) { this.dados.set(chave, String(valor)); }
removeItem(chave) { this.dados.delete(chave); }
clear() { this.dados.clear(); }
}
const storage = storageDisponivel() ? localStorage : new MemoryStorage();
7. Observando Mudanças e Depuração
Evento Storage para Comunicação Entre Abas
// Em qualquer aba do mesmo domínio
window.addEventListener('storage', (evento) => {
console.log(`Chave alterada: ${evento.key}`);
console.log(`Valor antigo: ${evento.oldValue}`);
console.log(`Valor novo: ${evento.newValue}`);
console.log(`Origem: ${evento.url}`);
console.log(`Storage: ${evento.storageArea}`); // localStorage ou sessionStorage
// Atualizar estado da aplicação
if (evento.key === 'tema') {
document.body.className = evento.newValue;
}
});
// Disparar de outra aba
localStorage.setItem('tema', 'escuro'); // Isso dispara o evento em outras abas
Nota importante: O evento storage só é disparado em outras abas do mesmo domínio, não na aba que fez a alteração.
Ferramentas do Navegador
- Chrome/Edge: DevTools → Application → Local Storage / Session Storage
- Firefox: DevTools → Storage → Local Storage / Session Storage
- Safari: Develop → Show Web Inspector → Storage → Local Storage
Logging e Debugging
function storageLogger(tipo = 'localStorage') {
const storage = window[tipo];
const handler = {
get(target, prop, receiver) {
const valor = Reflect.get(target, prop, receiver);
if (typeof valor === 'function') {
return function(...args) {
console.log(`[${tipo}] ${prop}(${args.map(a => JSON.stringify(a)).join(', ')})`);
return valor.apply(target, args);
};
}
return valor;
}
};
return new Proxy(storage, handler);
}
// Uso (apenas para debug)
const localStorageDebug = storageLogger('localStorage');
localStorageDebug.setItem('teste', 'valor'); // Log: [localStorage] setItem("teste", "valor")
8. Exemplo Prático: Carrinho de Compras Persistente
Vamos construir um carrinho de compras completo usando React, Context API e LocalStorage:
// CarrinhoContext.jsx
import { createContext, useContext, useReducer, useEffect } from 'react';
const CarrinhoContext = createContext();
function carrinhoReducer(state, action) {
switch (action.type) {
case 'ADICIONAR_ITEM': {
const itemExistente = state.itens.find(i => i.id === action.payload.id);
if (itemExistente) {
return {
...state,
itens: state.itens.map(i =>
i.id === action.payload.id
? { ...i, quantidade: i.quantidade + 1 }
: i
)
};
}
return {
...state,
itens: [...state.itens, { ...action.payload, quantidade: 1 }]
};
}
case 'REMOVER_ITEM':
return {
...state,
itens: state.itens.filter(i => i.id !== action.payload)
};
case 'ATUALIZAR_QUANTIDADE':
return {
...state,
itens: state.itens.map(i =>
i.id === action.payload.id
? { ...i, quantidade: Math.max(0, action.payload.quantidade) }
: i
).filter(i => i.quantidade > 0)
};
case 'LIMPAR_CARRINHO':
return { ...state, itens: [] };
default:
return state;
}
}
export function CarrinhoProvider({ children }) {
const [state, dispatch] = useReducer(carrinhoReducer, { itens: [] }, () => {
// Inicializar do LocalStorage
try {
const salvos = localStorage.getItem('carrinho');
return salvos ? JSON.parse(salvos) : { itens: [] };
} catch {
return { itens: [] };
}
});
// Persistir no LocalStorage sempre que o estado mudar
useEffect(() => {
try {
localStorage.setItem('carrinho', JSON.stringify(state));
} catch (erro) {
console.error('Erro ao salvar carrinho:', erro);
}
}, [state]);
const valor = { state, dispatch };
return (
<CarrinhoContext.Provider value={valor}>
{children}
</CarrinhoContext.Provider>
);
}
export function useCarrinho() {
const context = useContext(CarrinhoContext);
if (!context) {
throw new Error('useCarrinho deve ser usado dentro de CarrinhoProvider');
}
return context;
}
// Componente Produto.jsx
function Produto({ produto }) {
const { dispatch } = useCarrinho();
return (
<div className="produto">
<h3>{produto.nome}</h3>
<p>R$ {produto.preco.toFixed(2)}</p>
<button onClick={() => dispatch({ type: 'ADICIONAR_ITEM', payload: produto })}>
Adicionar ao Carrinho
</button>
</div>
);
}
// Componente Carrinho.jsx
function Carrinho() {
const { state, dispatch } = useCarrinho();
const total = state.itens.reduce(
(acc, item) => acc + item.preco * item.quantidade, 0
);
return (
<div className="carrinho">
<h2>Carrinho de Compras</h2>
{state.itens.length === 0 ? (
<p>Carrinho vazio</p>
) : (
<>
{state.itens.map(item => (
<div key={item.id} className="item-carrinho">
<span>{item.nome}</span>
<div className="controles">
<button
onClick={() => dispatch({
type: 'ATUALIZAR_QUANTIDADE',
payload
: {
id: item.id,
quantidade: item.quantidade - 1
})}
}>
-
</button>
<span>{item.quantidade}</span>
<button
onClick={() => dispatch({
type: 'ATUALIZAR_QUANTIDADE',
payload: {
id: item.id,
quantidade: item.quantidade + 1
}
})}
>
+
</button>
<button
onClick={() => dispatch({
type: 'REMOVER_ITEM',
payload: item.id
})}
>
Remover
</button>
</div>
<span>R$ {(item.preco * item.quantidade).toFixed(2)}</span>
</div>
))}
<div className="total">
<strong>Total: R$ {total.toFixed(2)}</strong>
</div>
<button onClick={() => dispatch({ type: 'LIMPAR_CARRINHO' })}>
Limpar Carrinho
</button>
</>
)}
</div>
);
}
// App.jsx - Integração completa
function App() {
const produtos = [
{ id: 1, nome: 'Camiseta', preco: 49.90 },
{ id: 2, nome: 'Calça Jeans', preco: 129.90 },
{ id: 3, nome: 'Tênis', preco: 199.90 },
{ id: 4, nome: 'Boné', preco: 29.90 }
];
return (
<CarrinhoProvider>
<div className="app">
<header>
<h1>Loja Online</h1>
</header>
<main>
<section className="produtos">
<h2>Produtos</h2>
<div className="grid-produtos">
{produtos.map(produto => (
<Produto key={produto.id} produto={produto} />
))}
</div>
</section>
<aside>
<Carrinho />
</aside>
</main>
</div>
</CarrinhoProvider>
);
}
Sincronização Entre Abas
Para garantir que o carrinho seja atualizado em tempo real quando o usuário abrir múltiplas abas:
// hook useCarrinhoSync.js
function useCarrinhoSync() {
const { state, dispatch } = useCarrinho();
useEffect(() => {
function handleStorageChange(evento) {
if (evento.key === 'carrinho') {
try {
const novosDados = JSON.parse(evento.newValue);
if (novosDados && novosDados.itens) {
// Recarregar estado completo
dispatch({ type: 'LIMPAR_CARRINHO' });
novosDados.itens.forEach(item => {
dispatch({ type: 'ADICIONAR_ITEM', payload: item });
});
}
} catch (erro) {
console.error('Erro ao sincronizar carrinho:', erro);
}
}
}
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, [dispatch]);
}
Conclusão
LocalStorage e SessionStorage são ferramentas essenciais para o desenvolvimento web moderno, oferecendo armazenamento simples e eficiente no lado do cliente. Enquanto o LocalStorage é ideal para dados que precisam persistir entre sessões (como preferências do usuário, carrinhos de compras e configurações), o SessionStorage brilha em cenários onde os dados devem ser efêmeros e específicos de uma aba (como formulários multi-etapas e dados temporários de checkout).
A combinação com React, especialmente através de hooks customizados e Context API, permite criar experiências fluidas onde o estado da aplicação sobrevive a recarregamentos de página sem depender de chamadas ao servidor. No entanto, é crucial lembrar das limitações de segurança: nunca armazene dados sensíveis como senhas, tokens de autenticação ou informações pessoais identificáveis nesses storages, pois eles são vulneráveis a ataques XSS.
Com as técnicas apresentadas — desde a serialização adequada de dados complexos até o tratamento de fallbacks para navegadores restritos — você está preparado para implementar armazenamento local robusto em suas aplicações JavaScript, seja no frontend puro ou em ecossistemas React.