image

Acesse bootcamps ilimitados e +650 cursos

50
%OFF
Article image

LD

Luiz Dornelas14/08/2023 16:10
Compartilhe

Conheça o Zustand - Gerenciamento de Estado Simplificado para Aplicativos React

  • #React

Introdução

Em aplicações React o gerenciamento de estados é importante independente de quão escalonável seu projeto é. Para facilitar esse gerenciamento, o React atualmente tem muitas bibliotecas, o mais famoso é o Redux.

No decorrer do tempo foram surgindo outras bibliotecas para ajudar no gerenciamento, e o Zustand é umas delas.

O objetivo desse artigo é apresentar o Zustand e como pode ser interessante a adição dele em um projeto principalmente pela rapidez, por ser pequeno e escalonável.

Começando com o Zustand

Como o Zustand é uma biblioteca javascript nós precisamos de algumas ferramentas básicas.

  • NodeJs - Para instalar o NodeJs basta acessar o link e realizar o donwload.
  • npm ou yarn - Os dois são gerenciadores de pacotes para o Node, o npm já vem incluído quando o Node é instalado, más se tiver preferencia pelo yarn, pode rodar o comando npm i yarn -g para instalar globalmente.

Então vamos começar com um projeto simples:

Para a criação desse projeto eu vou usar o Vite que é basicamente uma ferramenta de construção que fornece umas melhor experiência no desenvolvimento.

Executar o comando no terminal:

Com NPM:

npm create vite@latest

Com YARN:

yarn create vite

Ao executar o comando devemos dar nome ao projeto.

Project name >> DIO-Zustand
Package name >> dio-zustand

O Vite apresenta algumas opções de framework, Nesse caso iremos escolher o React apenas com Javascript.

Select a framework >> React
Select a variant >> Javascript

E agora executamos em sequência os comandos:

cd DIO-Zustand
npm install

Agora devemos instalar o Zustand

Com NPM:

npm install zustand

Com YARN:

yarn add zustand

Digite a linha de comando abaixo para executar o projeto

npm run dev

Para acessar o servidor local basta segurar o ctrl e clicar no link localhost:

➜  Local:   http://localhost:5173/
➜  Network: use --host to expose
➜  press h to show help

Sua aplicação no navegador será assim:

image

Agora vamos fazer algumas modificações para facilitar.

Abra o componente App.jsx dentro da pasta src e deixe-a igual o código abaixo:

import {useState} from "react"
import "./App.css"

function App() {
const [count,setCount] = useState(0)

return (
  <div className="card">
    <button onClick={() => setCount((count) => count + 1)}> 
    count is {count} 
    </button>
  </div>
)
}
              
export default App

Vamos criar uma dentro da pasta src uma pasta chamada store, e dentro dessa pasta criar um arquivo chamada useCountStore.js

A estrutura da pasta src ficará assim:

src/
┣ assets/
┣ store/
┃ ┗ useCountStore.js
┣ App.css
┣ App.jsx
┣ index.css
┗ main.jsx

Agora vamos construir a nossa store:

import { create } from "zustand"

const useCountStore = create((set) => ({
count: 0
}));

export default useCountStore

A função create passa a função set como callback e é ela que é responsável pelo atualização do estado dentro da store.

Agora vamos modificar o componente App.jsx:

Importamos a store dentro do <App/> e para acessar o estado, nós criamos uma variável que recebe o useCountStore hook e usa uma função de callback que retorna um pedaço do estado da store.

import useCountStore from "./store/useCountStore";
import "./App.css";

function App() {
const count = useCountStore((state) => state.count);

return (
  <div className="card">
    <button>count is {count}</button>
  </div>
);
}

export default App;

Agora precisamos atualizar este estado:

Dentro do useCountStore.js nós devemos acrescentar novos estados que irão usar a função set() para atualizar o estado de forma imutável.

import { create } from "zustand"

const useCountStore = create((set) => ({
count: 0,
increment: () => set((state) => ({count:state.count + 1})),
decrement: () => set((state) => ({count:state.count - 1})),
}));

export default useCountStore

Agora vamos atualizar o <App/>.

import useCountStore from "./store/useCountStore";
import "./App.css";

function App() {
const count = useCountStore((state) => state.count);
const increment = useCountStore((state) => state.increment);
const decrement = useCountStore((state) => state.decrement);

return (
  <div className="card">
    <button onClick={decrement}>Decrement</button>
    <p>{count}</p>
    <button onClick={incremente}>Increment<button>
  </div>
);
}

export default App;

O componente será renderizado quando o estado for alterado.

Podemos também alterar o estado de um componente de fora dele.

Vamos criar uma pasta chamada components dentro da pasta src. Dentro dessa pasta vamos criar um componente chamado Counter.jsx e importar a useCountStore.

import React from "react"
import useCountStore from "../store/useCountStore"

export const Counter = () => {
const count = useCountStore((state) => state.count);
return <div>{count}</div>;
};

Agora vamos importar o <Counter/> no <App/>.

import useCountStore from "./store/useCountStore";
import Counter from "./components/Counter";
import "./App.css";

function App() {
const increment = useCountStore((state) => state.increment);
const decrement = useCountStore((state) => state.decrement);

return (
  <div className="card">
    <button onClick={decrement}>Decrement</button>
    <Counter/>
    <button onClick={incremente}>Increment<button>
  </div>
);
}

export default App;

Assim nós não precisamos passar o count como prop e todo click que ocorrer nos botões o estado será alterado e apenas o <Counter/> será re-renderizado.

Evitando o Prop Drilling

Prop Drilling é basicamente quando passamos as props de um componente pai para um componente filho e esse componente passa essas props para o seu filho e assim por diante.

image

Para simular essa situação dentro da pasta components devemos criar os seguintes componente, Container.jsx, Card.jsx, Message.jsx.

App.jsx

import { useState } from "react";
import "./App.css";
import { Container } from "./components/Container";
import { Counter } from "./components/Counter";


function App() {
const [count, setCount] = useState(0);

return (
  <>
    <Counter count={count} />
    <Container count={count} setCount={setCount} />
  </>
);
}


export default App;

Container.jsx

import React from "react";
import { Card } from "./Card";


export const Container = ({ count, setCount }) => {
return (
  <div className="zone-container">
    <h1>Container</h1>
    <Card count={count} setCount={setCount} />
  </div>
);
};

Card.jsx

import React from "react";
import { Message } from "./Message";


export const Card = ({ count, setCount }) => {
return (
  <div className="zone-container">
    <h2>Card</h2>
    <Message count={count} setCount={setCount} />
  </div>
);
};

Message.jsx

import React from "react";

export const Message = ({ count, setCount }) => {

const increment = () => {
  setCount((prev) => prev + 1);
};

const decrement = () => {
  setCount((prev) => prev + 1);
};

return (
  <div className="zone-container">
    <h4>Message counter: {count}</h4>
    <button onClick={decrement}>decrement</button>
    <button onClick={increment}>increment</button>
  </div>
);
};

Counter.jsx

import React from "react";

export const Counter = ({ count }) => {
return (
  <div className="zone-container">
    <h3>Contador</h3>
    <h1>{count}</h1>
  </div>
);
};

A pasta src ficará com esta estrutura:

src/
┣ assets/
┣ components/
┃ ┣ Card.jsx
┃ ┣ Container.jsx
┃ ┣ Counter.jsx
┃ ┗ Message.jsx
┣ store/
┃ ┗ useCountStore.js
┣ App.css
┣ App.jsx
┣ index.css
┗ main.jsx

Neste caso nós estamos passando count e setCount como prop para o <Container/> depois para o <Card/> e por fim para o <Message/> onde são finalmente utilizados.

Isso causa renderizações desnecessárias quando o estado é alterado.

image

Para evitar estes comportamentos nós podemos usar o Zustand, importando a store dentro dos componentes que precisam dos estados.

Os componente ficarão assim:

App.jsx

import "./App.css";
import { Container } from "./components/Container";
import { Counter } from "./components/Counter";


function App() {
return (
  <>
    <Counter />
    <Container />
  </>
);
}


export default App;

Container.jsx

import React from "react";
import { Card } from "./Card";


export const Container = () => {


return (
  <div className="zone-container">
    <h1>Container</h1>
    <Card />
  </div>
);
};

Card.jsx

import React from "react";
import { Message } from "./Message";


export const Card = () => {


return (
  <div className="zone-container">
    <h2>Card</h2>
    <Message />
  </div>
);
};

Message.jsx

import React from "react";
import useCountStore from "../store/useCountStore";


export const Message = () => {
const count = useCountStore((state) => state.count);
const increment = useCountStore((state) => state.increment);
const decrement = useCountStore((state) => state.decrement);


return (
  <div className="zone-container">
    <h4>Message counter: {count}</h4>
    <button onClick={decrement}>decrement</button>
    <button onClick={increment}>increment</button>
  </div>
);
};

Counter.jsx

import React from "react";
import useCountStore from "../store/useCountStore";


export const Counter = () => {
const count = useCountStore((state) => state.count);
return (
  <div className="zone-container">
    <h3>Contador</h3>
    <h1>{count}</h1>
  </div>
);
};

Então o fluxo da aplicação será, onde será re-renderizado apenas os componentes <Counter/> e <Message/>:

image

Neste caso nós poderiamos usar o context API do react, mas o Zustand oferece algumas vantagens como:

  • Menos boilerplate.
  • Renderização de componentes só quando os estados forem alterados.
  • Gerenciamento de estado centralizado e baseado em ação.

Conclusão

O Zustand tem muito mais a oferecer, como funções assíncronas, alguns middlewares, suporte ao typescript, testes e Persisting Store.

Essa foi uma breve amostra de como o Zustand é fácil e divertido de usar para tarefas que às vezes pode parecer intimidadora.

Recursos adicionais

  • Link para a documentação oficial do Zustand
  • Link para a página oficial do Vite
  • Link para a página oficial do NodeJs
  • Link para a página oficial do Yarn
Compartilhe
Comentários (0)