├── .gitignore ├── README.md ├── eslint.config.mjs ├── next.config.ts ├── package-lock.json ├── package.json ├── public ├── file.svg ├── globe.svg ├── lucas.jpeg ├── next.svg ├── vercel.svg └── window.svg ├── src ├── app │ ├── favicon.ico │ ├── layout.tsx │ ├── not-found.ts │ └── post │ │ └── page.tsx ├── components │ ├── Avatar │ │ ├── Avatar.module.scss │ │ └── Avatar.tsx │ ├── CodeBlock │ │ ├── CodeBlock.module.scss │ │ └── CodeBlock.tsx │ ├── Footer │ │ ├── Footer.module.scss │ │ └── Footer.tsx │ ├── Header │ │ ├── Header.module.scss │ │ └── Header.tsx │ ├── Icon │ │ ├── Icon.module.scss │ │ ├── Icon.tsx │ │ └── icons │ │ │ ├── Github.tsx │ │ │ └── Linkedin.tsx │ ├── RenderCount │ │ ├── RenderCount.module.scss │ │ └── RenderCount.tsx │ └── Switch │ │ ├── Switch.module.scss │ │ └── Switch.tsx ├── middleware.ts ├── sections │ └── Home │ │ ├── Home.module.scss │ │ ├── Home.tsx │ │ ├── components │ │ ├── ContextPOC │ │ │ ├── ContextPOC.module.scss │ │ │ ├── ContextPOC.tsx │ │ │ ├── components │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ └── providers │ │ │ │ └── SwitcherProvider.tsx │ │ ├── StatePOC │ │ │ ├── StatePOC.module.scss │ │ │ ├── StatePOC.tsx │ │ │ └── components │ │ │ │ ├── Container │ │ │ │ ├── Container.module.scss │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ ├── Title.module.scss │ │ │ │ └── Title.tsx │ │ ├── StateWithMemoPOC │ │ │ ├── StateWithMemoPOC.module.scss │ │ │ ├── StateWithMemoPOC.tsx │ │ │ └── components │ │ │ │ ├── Container │ │ │ │ ├── Container.module.scss │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ ├── Title.module.scss │ │ │ │ └── Title.tsx │ │ ├── ZustandPOC │ │ │ ├── ZustandPOC.module.scss │ │ │ ├── ZustandPOC.tsx │ │ │ ├── components │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ └── stores │ │ │ │ └── SwitcherStore.tsx │ │ └── _StressTest │ │ │ ├── StressGroup.module.scss │ │ │ ├── StressGroup.tsx │ │ │ ├── components │ │ │ ├── StateStress │ │ │ │ ├── StateStress.module.scss │ │ │ │ ├── StateStress.tsx │ │ │ │ └── components │ │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ ├── StateWithMemoStress │ │ │ │ ├── StateWithMemoStress.module.scss │ │ │ │ ├── StateWithMemoStress.tsx │ │ │ │ └── components │ │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ └── ZustandStress │ │ │ │ ├── ZustandStress.module.scss │ │ │ │ ├── ZustandStress.tsx │ │ │ │ ├── components │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ │ └── stores │ │ │ │ └── useSwitcherStore.tsx │ │ │ └── shared │ │ │ ├── Child │ │ │ └── Child.tsx │ │ │ └── MemoizedChild │ │ │ └── MemoizedChild.tsx │ │ └── constants.ts ├── styles │ └── globals.scss └── utils │ └── classNames.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # State Management POC 2 | 3 | Esse projeto basicamente compara como diferentes abordagens de gerenciamento de estado no React impactam as re-renderizações dos componentes. O objetivo é ajudar desenvolvedores a entender como useState, Context API e Zustand se comportam em termos de performance e quando é mais apropriado usar cada um. 4 | 5 | ### 🚀 Propósito 6 | 7 | Este repositório foi criado para: 8 | • Demonstrar como o gerenciamento de estado no React afeta as re-renderizações; 9 | • Comparar useState, Context API e Zustand; 10 | • Servir como um guia prático para ajudar desenvolvedores a decidir qual abordagem escolher para diferentes casos de uso; 11 | 12 | --------- 13 | 14 | ### 📁 Estrutura do Projeto 15 | 16 | O repositório contém quatro exemplos, cada um focado em uma abordagem específica de gerenciamento de estado: 17 | 1. `useState` 18 | Mostra o comportamento básico do useState e como ele lida com o estado local; 19 | 2. `useState & React.memo` 20 | Mesma abordagem anterior mas com um plus de como otimizar o uso do `useState`; 21 | 3. `createContext` 22 | Demonstra como a Context API gerencia o estado global e como isso afeta as re-renderizações dos componentes filhos; 23 | 4. `zustand` 24 | Explora o comportamento do Zustand, destacando sua granularidade e eficiência na notificação de mudanças; 25 | 26 | --------- 27 | 28 | ### 📦 Instalação 29 | 30 | Siga os passos abaixo para rodar o projeto localmente: 31 | 1. Clone o repositório: 32 | ```bash 33 | git clone https://github.com/lucasca2/state-poc.git 34 | cd state-poc 35 | ``` 36 | 37 | 2. Instale as dependências: 38 | ```bash 39 | npm install 40 | ``` 41 | 42 | 3. Rode o projeto: 43 | ```bash 44 | npm run dev 45 | ``` 46 | 47 | 4. Acesse no navegador: http://localhost:3000 48 | 49 | --------- 50 | 51 | ### 📚 Recursos Adicionais 52 | 53 | Links úteis sobre gerenciamento de estado no React: 54 | - [Documentação oficial do React - State e Lifecycle](https://react.dev/learn/state-a-component-s-memory) 55 | - [Documentação oficial da Context API](https://react.dev/learn/passing-data-deeply-with-context) 56 | - [Documentação do Zustand](https://zustand-demo.pmnd.rs/docs/getting-started) 57 | 58 | --------- 59 | 60 | ### 💡 Autor 61 | 62 | Criado por [Lucas Costa Amaral](https://lucas.amaral.dev.br). 63 | Se você achou útil, não esqueça de dar uma ⭐ no repositório e compartilhar com outros devs! 64 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | reactStrictMode: false, 6 | }; 7 | 8 | export default nextConfig; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poc-context", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "15.1.2", 13 | "prism-react-renderer": "^2.4.1", 14 | "react": "^19.0.0", 15 | "react-code-blocks": "^0.1.6", 16 | "react-dom": "^19.0.0", 17 | "sass": "^1.83.0", 18 | "zustand": "^5.0.2" 19 | }, 20 | "devDependencies": { 21 | "@eslint/eslintrc": "^3", 22 | "@types/node": "^20", 23 | "@types/react": "^19", 24 | "@types/react-dom": "^19", 25 | "eslint": "^9", 26 | "eslint-config-next": "15.1.2", 27 | "typescript": "^5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/lucas.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasca2/state-poc/c06df59b3e573a5f1860c22864e91eb1586acd1a/public/lucas.jpeg -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasca2/state-poc/c06df59b3e573a5f1860c22864e91eb1586acd1a/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import '../styles/globals.scss'; 3 | 4 | export const metadata: Metadata = { 5 | title: "Gerenciar estados no React.js", 6 | description: "Aprenda a gerenciar estados no React.js de forma eficiente.", 7 | }; 8 | 9 | export default async function RootLayout({ 10 | children, 11 | }: Readonly<{ 12 | children: React.ReactNode; 13 | }>) { 14 | return ( 15 | 16 |
17 | {children} 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/not-found.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | 3 | 4 | export default function NotFound() { 5 | redirect('https://www.linkedin.com/feed/update/urn:li:activity:7276945157403406336/') 6 | } -------------------------------------------------------------------------------- /src/app/post/page.tsx: -------------------------------------------------------------------------------- 1 | import { Home } from "@/sections/Home/Home"; 2 | 3 | export default function PostPage() { 4 | return ( 5 |14 | {tokens.map((line, i) => ( 15 |22 | )} 23 |16 | {line.map((token, key) => ( 17 | 18 | ))} 19 |20 | ))} 21 |
React.js
31 |
33 | Primeiramente, vamos pensar em um cenário simples onde temos que
34 | gerenciar um estado que será compartilhado entre dois componentes.
35 |
36 | Então teremos um componente pai, que irá renderizar dois componentes
37 | filhos, e esses componentes filhos precisam compartilhar o mesmo estado.
38 |
39 | Um deles vai receber apenas um texto (que poderia vir de uma API) e o
40 | outro vai ser um Switcher
que vai servir apenas pra
41 | conseguir simular essa mudança de estado.
42 |
useState
;
46 |
48 | Acredito que a primeira coisa que vem à mente é usar o{" "}
49 | useState
para gerenciar o estado no componente pai e passar
50 | os valores e funções para os componentes filhos, que é a abordagem mais
51 | comum de gerenciamento de estados.
52 |
53 | Algo mais ou menos assim:
54 |
58 | Se notar nessa abordagem, cada vez que o state do{" "}
59 | switcherValue
muda, todos os componentes são
60 | re-renderizados novamente, até mesmo o Title
que não tem
61 | nada a ver com isso!
62 |
64 | Isso acontece por que toda vez que um estado muda, o componente que o
65 | contém é re-renderizado, e como o App
é o componente pai de
66 | todos os outros, todos eles são re-renderizados juntos.
67 |
69 | Existe uma forma de otimizar isso utilizando o React.memo
,
70 | que basicamente faz com que um componente só seja re-renderizado se
71 | alguma propriedade que ele recebe for alterada.
72 |
73 | Algo mais ou menos assim:
74 |
77 | Com essa simples abordagem, ele já resolve o problema de re-renderizar o{" "}
78 | Title
toda vez que o switcherValue
muda.
79 |
82 | Porém note que o App
continua re-renderizando toda vez que
83 | o switcherValue
muda... Isso é um problema? não
84 | necessariamente, por que nesse caso o unico componente que vai ter
85 | impacto de fato é o Container
, que é o esperado. O{" "}
86 | App
é re-renderizado, mas ele só vai causar o re-render do{" "}
87 | Container
que realmente deve ser re-renderizado, já que seu
88 | estado mudou, enquanto o Title
vai continuar lá intacto por
89 | conta do React.memo
.
90 |
createContext
;
94 |
96 | Um problema que temos quando começamos a usar a abordagem de{" "}
97 | useState
é que a medida que a aplicação cresce, a
98 | quantidade de props que precisamos passar para os componentes filhos
99 | também cresce, e isso pode se tornar um problema a longo prazo.
100 |
102 | E então é que surge a brilhante ideia de resolver isso utilizando um{" "}
103 | Context
do React, que basicamente é um objeto que vai ser
104 | compartilhado entre todos os componentes que estão dentro dele.
105 |
107 | Mas ao mesmo tempo que o código fica muito mais “limpo“ e organizado, 108 | ele também é muito perigoso e pode ser um inimigo pra performance da sua 109 | aplicação. 110 |
111 |
112 | Vamos pegar e passar toda essa lógica do state e colocar dentro de um{" "}
113 | Context
então pra ver como ele se comporta.
114 |
118 | Então, como podemos ver, o código realmente fica visualmente mais limpo,
119 | por que não precisamos mais passar as props para os componentes filhos.
120 |
121 | Dentro de cada componente filho a gente simplesmente chamaria o contexto
122 | e pegaria o estado que a gente precisa.
123 |
127 | Muito mais organizado, mas vamos ver na prática como isso se comporta? 128 |
129 |
131 | Note que o App
realmente parou de re-renderizar, ok! Mas o{" "}
132 | Title
está re-renderizando toda vez que o{" "}
133 | switcherValue
muda, e isso é um problema.
134 |
136 | Isso acontece por que o React
não consegue identificar qual
137 | estado do contexto aquele componente depende, então ele re-renderiza
138 | toda vez que o estado do contexto muda. E não há React.memo
{" "}
139 | que resolva, por que nesse caso não tem nenhuma propriedade que muda, o
140 | que muda é o estado do contexto.
141 |
Como resolver isso então? Bom, a resposta é: Depende...
143 |
144 | Em alguns casos a gente pode dividir o contexto em vários contextos
145 | menores, e assim a gente consegue controlar melhor o que cada componente
146 | depende. Mas essa abordagem não é muito escalável, por que se o estado é
147 | muito complexo, acaba que é necessário criar diversos contextos e acaba
148 | deixando o código muito complexo e confuso.
149 |
150 | Então geralmente o Context
é mais recomendado para estados
151 | que são mais simples ou para estados que não são alterados
152 | frequentemente como um tema(light/dark) por exemplo.
153 |
Zustand
;
156 |
158 | Se usar useState
, fica muito verboso, se usar{" "}
159 | createContext
, não fica performático, o que usar então?
160 |
162 | Nesses casos, a gente pode optar por usar um gerenciador de estados mais 163 | robusto, como por exemplo um Redux, Zustand, etc...{" "} 164 |
165 |
166 | A diferença entre esses gerenciadores de estados robustos e o{" "}
167 | Context
é que a gente consegue observar apenas um pedaço do
168 | estado invés de sempre observar o estado inteiro.
169 |
Nesse exemplo vou usar o Zustand pela facilidade da implementação:
171 |174 | Note que até mesmo nosso arquivo principal fica mais limpo e organizado, 175 | por que ele não precisa saber sobre os estados dos componentes filhos. 176 |
177 |
180 | Dessa forma, o componente Title
vai re-renderizar apenas se
181 | o estado title
for alterado, por que agora ele não está
182 | mais observando o restante dos estados
183 |
185 | O mesmo vale pro Container
, se por alguma razão o estado{" "}
186 | title
fosse alterado, isso não o afetaria em nada.
187 |
Mas vamos ver na prática como isso fica:
189 |
191 | Usando um gerenciador mais robusto, a gente até consegue evitar o uso do{" "}
192 | React.memo
por que não é mais necessário já que o
193 | componente pai não está mais sendo re-renderizado desnecessariamente
194 | (como acontece usando o useState
)
195 |
198 | Na maioria das vezes, um simples useState
é o suficiente
199 | pra resolver os problemas simples do dia a dia, se souber trabalhar bem
200 | com ele, ele é tão performático quanto qualquer outro gerenciador de
201 | estados.
202 |
204 | Porém usar React.memo
demais não é muito legal, por que ele
205 | também tem um custo de performance, então é sempre bom usar com
206 | moderação.
207 |
209 | Do mesmo jeito também que não é legal usar um Redux/Zustand/etc.. pra 210 | qualquer estado simples da nossa aplicação, a gente estaria adicionando 211 | uma complexidade desnecessária. 212 |
213 |214 | Mas que custo é esse? falar é fácil, então vamos fazer um teste de 215 | stress e ver na prática! 216 |
217 |
224 | Em resumo, a melhor abordagem vai depender do seu caso de uso, se você
225 | tem um estado simples, use um useState
, se você tem um
226 | estado mais complexo, use um gerenciador de estados mais robusto. Se
227 | você tem um estado interno e quer usar um useState
de uma
228 | forma mais limpa, use um Context
.
229 |
231 | Na maioria das vezes o useState
é o suficiente, se você
232 | notar que seu código está começando a ficar muito complexo e confuso, aí
233 | sim é hora de pensar em usar um gerenciador de estados mais robusto.
234 |
236 | Mas tome cuidado com re-renderizações desnecessárias antes que vire uma 237 | bola de neve e você não consiga mais controlar. 238 |
239 |240 | Fique a vontade pra instalar o{" "} 241 | repositório e fazer mais testes por 242 | conta própria! 243 |
244 | 245 |