lendo a documentação do Zustand pela primeira vez

eis que ando trabalhando com React no front-end e agora o foco da equipe é usar Zustand pra fazer gerenciamento de estados, deixando o Redux de lado.

beleza, preciso aprender esse trem. então vamo lá dar uma lida na documentação. esse é só um registro pessoal do início dos meus estudos 🙂

getting started

a primeira coisa que a documentação comenta é sobre o store. o store é um hook em que dá pra definir estados, funções, etc. pra serem consumidos nos componentes. é o seu “mercado” de possibilidades, que oferece contextos diversos pra aplicação.

import { create } from 'zustand'

const useStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears) => set({ bears: newBears }),
}))

pra consumir a informação dos stores no seu componente, a documentação aponta o uso do hook useStore:

function BearCounter() {
const bears = useStore((state) => state.bears)
return <h1>{bears} bears around here…</h1>
}


function Controls() {
const increasePopulation = useStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}

comparison

aqui a documentação compara o Zustand com outras libs de gerenciamento de estado, particularmente nos tópicos state model e render optimization. resumindo aqui as comparações na tabela abaixo:

ferramentastate modelrender optimization
Reduxbaseado num modelo de dados imutável, assim como o Zustand. contudo, o Redux requer que o app seja encapsulado em providers de contexto (o Zustand não)em ambos Zustand e Redux é recomendado que você manualmente aplique otimizações de render usando seletores
Valtiobaseado num modelo de dados mutável, diferente do Zustando Valtio usa proxies do Javascript para rastrear automaticamente quais propriedades do estado são lidas em cada componente, enquanto o Zustand recomenda que as otimizações sejam feitas através de seletores
Jotaio Zustand trabalha com uma store única, enquanto o Jotai consiste em átomos primitivos que podem ser combinados entre sio Jotai faz otimizações de renderização por meio da dependência de átomos, enquanto o Zustand recomenda que as otimizações sejam feitas através de seletores
RecoilRecoil depende de string keys e precisa envolver o aplicativo em um provider de contextoo Recoil faz otimizações de renderização por meio da dependência de átomos, enquanto o Zustand recomenda que as otimizações sejam feitas através de seletores

updating state

pra fazer atualizações simples, a função set dá conta. ela consegue fazer atualizações no primeiro nível!

na store definimos uma ação que vai fazer a atualização do estado (no exemplo abaixo vamos considerar a ação updateFirstName):

const usePersonStore = create<State & Action>((set) => ({
firstName: ' ',
updateFirstName: (firstName) => set(() => ({ firstName: firstName })),
}))

no componente que consome a função da store:

function App() {
const firstName = usePersonStore((state) => state.firstName)
const updateFirstName = usePersonStore((state) => state.updateFirstName)

return (
<main>
<label> First name
<input
state onChange={(e) => updateFirstName(e.currentTarget.value)}
value={firstName} />
</label>
<p> Hello, <strong>{firstName}!</strong> </p>
</main>
) }

outro rolê mais específico é fazer updates de estado em um objeto profundamente aninhado, tipo esse:

type State = {
deep: {
nested: {
obj: { count: number }
}
}
}

semelhante ao React ou Redux, a abordagem normal é copiar cada nível do objeto de estado e usar spread operator pra mesclar os novos valores de estado. contudo, tem outras alternativas pra facilitar essa escrita de código: a documentação cita Immer, optics-ts e Ramda.

flux inspired practices

segundo a documentação, apesar do Zustand se considerar uma lib “unopinionated”, alguns padrões são recomendados:

1. single store

idealmente, o estado global da aplicação deve estar localizado em uma única store. contudo, se a aplicação for muuuito grande, o Zustand permite que a store seja dividida em slices menores.

2. use set / setState to update the store

sempre use set (ou setState) para realizar atualizações na store. essas funções garantem que a atualização seja corretamente mesclada e que quem está consumindo seja notificado adequadamente.

3. colocate store actions

no Zustand, o estado pode ser atualizado sem o uso de ações dispatch e redutores encontrados em outras bibliotecas. essas ações podem ser adicionadas diretamente na store:

const useBoundStore = create((set) => ({
storeSliceA: …,
storeSliceB: …,
storeSliceC: …,
updateX: () => set(…),
updateY: () => set(…),
}))

opcionalmente, usando setState, elas podem ser localizadas externamente à store.

practice with no storage actions

a documentação trabalha com duas abordagens pra alocar as ações na store, bora ver:

1. ações junto com os estados dentro da store

é o que o Zustand recomenda, a priori.

me PARECE mais organizado manter os estados e as ações unidos, porque talvez facilite manutenções, criação de novas ações etc. fica tudo encapsulado em um lugar só:

export const useBoundStore = create((set) => ({
count: 0,
text: 'hello',
inc: () => set((state) => ({ count: state.count + 1 })),
setText: (text) => set({ text }),
}))

nesse caso, você teria que chamar o hook pra consumir as ações.

2. ações no nível do módulo fora da store

essa abordagem alternativa não exige um hook pra consumir a ação, mas talvez seja menos encapsulada:

export const useBoundStore = create(() => ({
count: 0,
text: 'hello',
}))

export const inc = () =>
useBoundStore.setState((state) => ({ count: state.count + 1 }))

export const setText = (text) => useBoundStore.setState({ text })

segundo a documentação, não há pontos negativos em usar um ou outro, parece mais uma questão de preferência mesmo.

conclusiones

por hoje é isso, pessoal. foi massa entender um pouco melhor sobre stores e as possibilidades de consumo desse contexto.

boa noite!