<h2>Entendendo Jotai: Uma Alternativa Elegante ao Redux</h2>
<p>Jotai é uma biblioteca de gerenciamento de estado minimalista para React, desenvolvida com a filosofia de ser leve e intuitiva. Diferentemente de alternativas como Redux ou Zustand que trabalham com uma store centralizada, Jotai adota um modelo descentralizado baseado em <strong>atoms</strong> — pequenas unidades de estado que podem ser compostas e derivadas. Essa abordagem torna o código mais modular, porque cada atom é uma unidade independente que pode ser reutilizada em diferentes partes da aplicação sem acoplamento desnecessário.</p>
<p>A beleza do Jotai está na sua simplicidade: você define o estado de forma granular e deixa a biblioteca se encarregar de otimizações de renderização. React Components apenas renderizam quando os atoms que eles realmente usam mudam, evitando re-renders desnecessários de forma automática. Essa é uma vantagem significativa em comparação com Context API ou Redux, onde você frequentemente sofre com re-renders em cascata.</p>
<h2>Atoms: Construindo Blocos de Estado</h2>
<h3>O Conceito Fundamental de um Atom</h3>
<p>Um atom em Jotai é simplesmente um objeto que representa uma unidade de estado. Você cria atoms usando a função <code>atom()</code> e passa um valor inicial como argumento. Quando um atom muda, qualquer componente que o consome é notificado e renderizado novamente — mas apenas esse componente, graças ao sistema de dependências inteligente do Jotai.</p>
<pre><code class="language-javascript">import { atom } from 'jotai';
// Um atom simples com valor primitivo
const countAtom = atom(0);
// Um atom com um objeto
const userAtom = atom({
name: 'João',
email: 'joao@example.com',
age: 28
});
// Um atom com um array
const todoAtom = atom([
{ id: 1, text: 'Aprender Jotai', completed: false },
{ id: 2, text: 'Usar em produção', completed: false }
]);</code></pre>
<h3>Lendo e Escrevendo em Atoms</h3>
<p>Para interagir com atoms em componentes React, você usa o hook <code>useAtom()</code>. Esse hook retorna um array com dois elementos: o valor atual do atom e uma função para atualizar esse valor. É similar ao <code>useState()</code>, mas o estado é compartilhado globalmente entre todos os componentes que usam o mesmo atom.</p>
<pre><code class="language-javascript">import { useAtom } from 'jotai';
import { countAtom } from './atoms';
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<div>
<p>Contagem: {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
<button onClick={() => setCount(c => c - 1)}>Decrementar</button>
</div>
);
}</code></pre>
<p>Note que a função de atualização pode receber tanto um novo valor direto quanto uma função que recebe o valor anterior — exatamente como em <code>useState()</code>. Isso permite padrões funcionais elegantes quando você precisa fazer cálculos baseados no estado anterior.</p>
<h3>Operações Avançadas com Atoms</h3>
<p>Às vezes você quer atualizar múltiplos atoms de forma coordenada, ou executar efeitos colaterais quando um atom muda. O Jotai oferece a função <code>useAtomCallback()</code> para esses casos mais complexos.</p>
<pre><code class="language-javascript">import { atom, useAtomCallback } from 'jotai';
const firstNameAtom = atom('João');
const lastNameAtom = atom('Silva');
const fullNameAtom = atom('João Silva');
function ProfileUpdater() {
const updateProfile = useAtomCallback(
(get, set) => async (first, last) => {
// get() lê o valor atual de um atom
// set() escreve em um atom
set(firstNameAtom, first);
set(lastNameAtom, last);
set(fullNameAtom, ${first} ${last});
// Você pode fazer requisições, validações, etc.
await fetch('/api/profile', {
method: 'POST',
body: JSON.stringify({ first, last })
});
}
);
return (
<button onClick={() => updateProfile('Carlos', 'Santos')}>
Atualizar Perfil
</button>
);
}</code></pre>
<h2>Derived State: Computando Novos Estados a Partir de Atoms Existentes</h2>
<h3>Atoms Derivados com <code>atom()</code></h3>
<p>Um dos conceitos mais poderosos do Jotai é a capacidade de criar <strong>atoms derivados</strong> — atoms que dependem de outros atoms e são calculados automaticamente. Quando você passa uma função de leitura como argumento para <code>atom()</code>, está criando um atom que computa seu valor baseado em outros atoms.</p>
<pre><code class="language-javascript">import { atom } from 'jotai';
const priceAtom = atom(100);
const quantityAtom = atom(5);
// Atom derivado que calcula o total
const totalAtom = atom(
(get) => get(priceAtom) * get(quantityAtom)
);
function ShoppingCart() {
const [price] = useAtom(priceAtom);
const [quantity] = useAtom(quantityAtom);
const [total] = useAtom(totalAtom);
return (
<div>
<p>Preço: R$ {price}</p>
<p>Quantidade: {quantity}</p>
<p style={{ fontWeight: 'bold' }}>Total: R$ {total}</p>
</div>
);
}</code></pre>
<p>A chave aqui é que <code>totalAtom</code> não armazena seu próprio estado. Ele é <strong>read-only</strong> por padrão — apenas calcula seu valor sob demanda. Quando <code>priceAtom</code> ou <code>quantityAtom</code> mudam, o Jotai automaticamente recalcula <code>totalAtom</code> e notifica apenas os componentes que o usam.</p>
<h3>Atoms Derivados com Lógica de Escrita</h3>
<p>Às vezes você quer um atom derivado que também possa ser escrito. Imagine filtrar uma lista: você quer ler a lista completa, mas também quer poder manter um estado separado com filtros e retornar uma lista filtrada. O Jotai permite isso com uma função de escrita como segundo argumento.</p>
<pre><code class="language-javascript">import { atom } from 'jotai';
const allTodosAtom = atom([
{ id: 1, text: 'Estudar', completed: true },
{ id: 2, text: 'Exercitar', completed: false },
{ id: 3, text: 'Descansar', completed: false }
]);
const filterAtom = atom('all'); // 'all', 'completed', 'active'
// Atom derivado com lógica de leitura E escrita
const filteredTodosAtom = atom(
(get) => {
const todos = get(allTodosAtom);
const filter = get(filterAtom);
switch (filter) {
case 'completed':
return todos.filter(t => t.completed);
case 'active':
return todos.filter(t => !t.completed);
default:
return todos;
}
},
// Segunda função: escrever em atoms derivados
(get, set, newTodos) => {
set(allTodosAtom, newTodos);
}
);
function TodoApp() {
const [todos] = useAtom(filteredTodosAtom);
const [filter, setFilter] = useAtom(filterAtom);
return (
<div>
<div>
<button onClick={() => setFilter('all')}>Todos</button>
<button onClick={() => setFilter('active')}>Pendentes</button>
<button onClick={() => setFilter('completed')}>Completos</button>
</div>
<ul>
{todos.map(todo => (
<li key={todo.id} style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
{todo.text}
</li>
))}
</ul>
</div>
);
}</code></pre>
<h2>Async Atoms: Integrando Requisições HTTP e Operações Assíncronas</h2>
<h3>O Padrão Básico de Async Atoms</h3>
<p>Trabalhar com dados assíncronos em uma aplicação real é inevitável. Jotai torna isso elegante permitindo que atoms retornem Promises. Quando você usa um async atom, o hook <code>useAtom()</code> ainda funciona normalmente, mas o valor pode estar pendente (loading), completado (com dados), ou em erro.</p>
<pre><code class="language-javascript">import { atom } from 'jotai';
// Um atom simples que dispara uma requisição
const userDataAtom = atom(async () => {
const response = await fetch('/api/user/1');
if (!response.ok) throw new Error('Falha ao buscar usuário');
return response.json();
});
function UserProfile() {
const [user] = useAtom(userDataAtom);
// Se user for uma Promise, a suspensão automática de Jotai
// vai pausar a renderização até a Promise resolver
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// Você precisa envolver em um Suspense e ErrorBoundary
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<p>Carregando usuário...</p>}>
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
</Suspense>
);
}</code></pre>
<h3>Async Atoms Parametrizados</h3>
<p>Um padrão comum é ter async atoms que dependem de parâmetros — como buscar um produto baseado em um ID. Você pode implementar isso combinando um atom de parâmetro com um atom derivado assíncro.</p>
<pre><code class="language-javascript">import { atom } from 'jotai';
const productIdAtom = atom('1');
const productAtom = atom(async (get) => {
const id = get(productIdAtom);
const response = await fetch(/api/products/${id});
if (!response.ok) throw new Error('Produto não encontrado');
return response.json();
});
function ProductDetail() {
const [productId, setProductId] = useAtom(productIdAtom);
const [product] = useAtom(productAtom);
return (
<div>
<input
value={productId}
onChange={(e) => setProductId(e.target.value)}
placeholder="ID do produto"
/>
<div>
<h2>{product.name}</h2>
<p>Preço: R$ {product.price}</p>
<p>{product.description}</p>
</div>
</div>
);
}</code></pre>
<p>Quando <code>productIdAtom</code> muda, o Jotai automaticamente recalcula <code>productAtom</code> e dispara uma nova requisição. Não há necessidade de efeitos manually ou callbacks.</p>
<h3>Tratamento de Erros em Async Atoms</h3>
<p>O Jotai oferece a função <code>atomWithDefault()</code> e outras utilidades, mas a forma mais simples e explícita de lidar com erros é usar uma estrutura de dados que capture tanto sucesso quanto fracasso.</p>
<pre><code class="language-javascript">import { atom } from 'jotai';
const apiDataAtom = atom(async () => {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error(HTTP ${response.status});
return { status: 'success', data: await response.json() };
} catch (error) {
return {
status: 'error',
error: error instanceof Error ? error.message : 'Erro desconhecido'
};
}
});
function DataDisplay() {
const [result] = useAtom(apiDataAtom);
if (result.status === 'error') {
return <div style={{ color: 'red' }}>Erro: {result.error}</div>;
}
return (
<div>
<pre>{JSON.stringify(result.data, null, 2)}</pre>
</div>
);
}</code></pre>
<h3>Cache e Refetch em Async Atoms</h3>
<p>Para casos onde você quer fazer refetch manualmente ou implementar cache inteligente, combine async atoms com <code>useAtomCallback()</code>.</p>
<pre><code class="language-javascript">import { atom, useAtomCallback } from 'jotai';
const apiResponseAtom = atom(null);
const isLoadingAtom = atom(false);
const fetchDataAtom = useAtomCallback((get, set) => async () => {
set(isLoadingAtom, true);
try {
const response = await fetch('/api/items');
const data = await response.json();
set(apiResponseAtom, data);
} catch (error) {
set(apiResponseAtom, null);
console.error('Erro:', error);
} finally {
set(isLoadingAtom, false);
}
});
function DataManager() {
const [data] = useAtom(apiResponseAtom);
const [isLoading] = useAtom(isLoadingAtom);
const refetch = useAtomCallback(fetchDataAtom);
return (
<div>
<button onClick={() => refetch()} disabled={isLoading}>
{isLoading ? 'Carregando...' : 'Refetch'}
</button>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}</code></pre>
<h2>Conclusão</h2>
<p>Os três pilares que você aprendeu aqui — <strong>Atoms, Derived State e Async Atoms</strong> — formam a base para criar aplicações React escaláveis com estado gerenciado de forma limpa e performática. Atoms são suas unidades de estado minimalistas; derived atoms eliminam a necessidade de duplicar lógica de computação; e async atoms trazem requisições HTTP e operações assíncronas para dentro do mesmo modelo de state management, sem complicações extras.</p>
<p>A principal vantagem do Jotai em relação a outras soluções é a <strong>modularidade e granularidade</strong>: você define exatamente o que precisa, sem boilerplate, e a biblioteca se encarrega de otimizações. Não há ações, reducers ou despachadores — apenas estado e cálculos sobre esse estado.</p>
<p>Comece pequeno em seus projetos: crie alguns atoms para features isoladas, entenda como derived atoms economizam lógica, depois explore async atoms para integrar com sua API. O Jotai escala naturalmente conforme sua aplicação cresce.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://jotai.org/" target="_blank" rel="noopener noreferrer">Documentação Oficial do Jotai</a></li>
<li><a href="https://github.com/pmndrs/jotai" target="_blank" rel="noopener noreferrer">Jotai - GitHub Repository</a></li>
<li><a href="https://blog.logrocket.com/jotai-guide-react-state-management/" target="_blank" rel="noopener noreferrer">React State Management com Jotai - LogRocket</a></li>
<li><a href="https://jotai.org/docs/basics/primitives" target="_blank" rel="noopener noreferrer">Atoms and Derived State - Jotai Docs</a></li>
<li><a href="https://jotai.org/docs/api/core#async" target="_blank" rel="noopener noreferrer">Async in Jotai - Jotai API Reference</a></li>
</ul>
<p><!-- FIM --></p>