├── public ├── lucas.jpeg ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── src ├── app │ ├── favicon.ico │ ├── post │ │ └── page.tsx │ ├── not-found.ts │ └── layout.tsx ├── sections │ └── Home │ │ ├── components │ │ ├── ContextPOC │ │ │ ├── components │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ ├── ContextPOC.tsx │ │ │ ├── ContextPOC.module.scss │ │ │ └── providers │ │ │ │ └── SwitcherProvider.tsx │ │ ├── StatePOC │ │ │ ├── components │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ ├── StatePOC.module.scss │ │ │ └── StatePOC.tsx │ │ ├── ZustandPOC │ │ │ ├── components │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ ├── stores │ │ │ │ └── SwitcherStore.tsx │ │ │ ├── ZustandPOC.tsx │ │ │ └── ZustandPOC.module.scss │ │ ├── StateWithMemoPOC │ │ │ ├── components │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ ├── StateWithMemoPOC.module.scss │ │ │ └── StateWithMemoPOC.tsx │ │ └── _StressTest │ │ │ ├── shared │ │ │ ├── Child │ │ │ │ └── Child.tsx │ │ │ └── MemoizedChild │ │ │ │ └── MemoizedChild.tsx │ │ │ ├── components │ │ │ ├── ZustandStress │ │ │ │ ├── components │ │ │ │ │ ├── Container │ │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ │ └── Container.tsx │ │ │ │ │ └── Title │ │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ │ └── Title.tsx │ │ │ │ ├── stores │ │ │ │ │ └── useSwitcherStore.tsx │ │ │ │ ├── ZustandStress.module.scss │ │ │ │ └── ZustandStress.tsx │ │ │ ├── StateStress │ │ │ │ ├── components │ │ │ │ │ ├── Container │ │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ │ └── Container.tsx │ │ │ │ │ └── Title │ │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ │ └── Title.tsx │ │ │ │ ├── StateStress.module.scss │ │ │ │ └── StateStress.tsx │ │ │ └── StateWithMemoStress │ │ │ │ ├── components │ │ │ │ ├── Container │ │ │ │ │ ├── Container.module.scss │ │ │ │ │ └── Container.tsx │ │ │ │ └── Title │ │ │ │ │ ├── Title.module.scss │ │ │ │ │ └── Title.tsx │ │ │ │ ├── StateWithMemoStress.module.scss │ │ │ │ └── StateWithMemoStress.tsx │ │ │ ├── StressGroup.module.scss │ │ │ └── StressGroup.tsx │ │ ├── Home.module.scss │ │ ├── constants.ts │ │ └── Home.tsx ├── components │ ├── Icon │ │ ├── Icon.module.scss │ │ ├── Icon.tsx │ │ └── icons │ │ │ ├── Linkedin.tsx │ │ │ └── Github.tsx │ ├── Avatar │ │ ├── Avatar.module.scss │ │ └── Avatar.tsx │ ├── Footer │ │ ├── Footer.tsx │ │ └── Footer.module.scss │ ├── CodeBlock │ │ ├── CodeBlock.module.scss │ │ └── CodeBlock.tsx │ ├── Header │ │ ├── Header.module.scss │ │ └── Header.tsx │ ├── RenderCount │ │ ├── RenderCount.module.scss │ │ └── RenderCount.tsx │ └── Switch │ │ ├── Switch.tsx │ │ └── Switch.module.scss ├── styles │ └── globals.scss ├── utils │ └── classNames.ts └── middleware.ts ├── next.config.ts ├── eslint.config.mjs ├── .gitignore ├── tsconfig.json ├── package.json └── README.md /public/lucas.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasca2/state-poc/HEAD/public/lucas.jpeg -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasca2/state-poc/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/sections/Home/components/ContextPOC/components/Container/Container.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } -------------------------------------------------------------------------------- /src/sections/Home/components/StatePOC/components/Container/Container.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } -------------------------------------------------------------------------------- /src/sections/Home/components/ZustandPOC/components/Container/Container.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } -------------------------------------------------------------------------------- /src/sections/Home/components/StateWithMemoPOC/components/Container/Container.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } -------------------------------------------------------------------------------- /src/components/Icon/Icon.module.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | color: #fff; 3 | 4 | width: 24px; 5 | height: 24px; 6 | 7 | & svg { 8 | width: 100%; 9 | height: 100%; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/shared/Child/Child.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const Child = ({ value }: { value: string }) => { 4 | return
{value}
; 5 | }; 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/app/post/page.tsx: -------------------------------------------------------------------------------- 1 | import { Home } from "@/sections/Home/Home"; 2 | 3 | export default function PostPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /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/sections/Home/components/_StressTest/components/ZustandStress/components/Container/Container.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateStress/components/Container/Container.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div:not(:first-child) { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateWithMemoStress/components/Container/Container.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div:not(:first-child) { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } -------------------------------------------------------------------------------- /src/components/Avatar/Avatar.module.scss: -------------------------------------------------------------------------------- 1 | .image { 2 | object-fit: cover; 3 | object-position: top; 4 | border-radius: 100%; 5 | 6 | width: 64px; 7 | height: 64px; 8 | 9 | @media (max-width: 768px) { 10 | width: 48px; 11 | height: 48px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./Footer.module.scss"; 2 | 3 | export const Footer = () => { 4 | return ( 5 |
6 |
© 2024 - LUCAS COSTA AMARAL
7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /src/sections/Home/components/StateWithMemoPOC/components/Title/Title.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } 7 | 8 | .title { 9 | font-size: 20px; 10 | font-weight: bold; 11 | color: #ddd; 12 | margin-bottom: 10px; 13 | margin: 0; 14 | } 15 | -------------------------------------------------------------------------------- /src/sections/Home/components/ContextPOC/components/Title/Title.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } 7 | 8 | .title { 9 | font-size: 20px; 10 | font-weight: bold; 11 | color: #ddd; 12 | margin-bottom: 10px; 13 | margin: 0; 14 | } -------------------------------------------------------------------------------- /src/sections/Home/components/StatePOC/components/Title/Title.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } 7 | 8 | .title { 9 | font-size: 20px; 10 | font-weight: bold; 11 | color: #ddd; 12 | margin-bottom: 10px; 13 | margin: 0; 14 | } -------------------------------------------------------------------------------- /src/sections/Home/components/ZustandPOC/components/Title/Title.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } 7 | 8 | .title { 9 | font-size: 20px; 10 | font-weight: bold; 11 | color: #ddd; 12 | margin-bottom: 10px; 13 | margin: 0; 14 | } -------------------------------------------------------------------------------- /src/components/Footer/Footer.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 80px 0 16px 0; 5 | } 6 | 7 | .copyRight { 8 | display: flex; 9 | justify-content: center; 10 | font-size: 10px; 11 | color: #fff; 12 | opacity: .75; 13 | letter-spacing: 0.15rem; 14 | font-weight: 600; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/CodeBlock/CodeBlock.module.scss: -------------------------------------------------------------------------------- 1 | .code { 2 | width: 100%; 3 | padding: 32px; 4 | background-color: #111; 5 | border: 1px solid #111; 6 | border-radius: 8px; 7 | display: flex; 8 | flex-direction: column; 9 | gap: 12px; 10 | 11 | overflow-x: auto; 12 | 13 | .lineNumber { 14 | margin-right: 24px; 15 | } 16 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateStress/components/Title/Title.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } 7 | 8 | .title { 9 | font-size: 20px; 10 | font-weight: bold; 11 | color: #ddd; 12 | margin-bottom: 10px; 13 | margin: 0; 14 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/ZustandStress/components/Title/Title.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } 7 | 8 | .title { 9 | font-size: 20px; 10 | font-weight: bold; 11 | color: #ddd; 12 | margin-bottom: 10px; 13 | margin: 0; 14 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateWithMemoStress/components/Title/Title.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | > div { 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | } 7 | 8 | .title { 9 | font-size: 20px; 10 | font-weight: bold; 11 | color: #ddd; 12 | margin-bottom: 10px; 13 | margin: 0; 14 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/shared/MemoizedChild/MemoizedChild.tsx: -------------------------------------------------------------------------------- 1 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 2 | import React from "react"; 3 | 4 | export const MemoizedChild = React.memo(({ value }: { value: string }) => { 5 | return {value}; 6 | }); 7 | 8 | MemoizedChild.displayName = "MemoizedChild"; 9 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/sections/Home/components/ZustandPOC/stores/SwitcherStore.tsx: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | type SwitcherStore = { 4 | switcherValue: boolean; 5 | toggle: () => void; 6 | title: string; 7 | }; 8 | 9 | export const useSwitcherStore = create((set) => ({ 10 | title: "Zustand", 11 | switcherValue: false, 12 | toggle: () => set((state) => ({ switcherValue: !state.switcherValue })), 13 | })) -------------------------------------------------------------------------------- /src/components/Header/Header.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | align-items: center; 4 | 5 | width: 100%; 6 | justify-content: space-between; 7 | 8 | padding: 40px 0; 9 | } 10 | 11 | .socialWrapper { 12 | display: flex; 13 | gap: 32px; 14 | 15 | svg { 16 | opacity: .5; 17 | transition: opacity .3s; 18 | will-change: opacity; 19 | } 20 | 21 | svg:hover { 22 | opacity: 1; 23 | } 24 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/ZustandStress/stores/useSwitcherStore.tsx: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | type SwitcherStore = { 4 | title: string; 5 | switcherValue: boolean; 6 | toggle: () => void; 7 | }; 8 | 9 | export const useSwitcherStore = create((set) => ({ 10 | title: "Zustand", 11 | switcherValue: false, 12 | toggle: () => set((state) => ({ switcherValue: !state.switcherValue })), 13 | })) -------------------------------------------------------------------------------- /src/styles/globals.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | max-width: 100vw; 4 | overflow-x: hidden; 5 | } 6 | 7 | body { 8 | color: #fff; 9 | background: #000; 10 | font-family: Arial, Helvetica, sans-serif; 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | } 14 | 15 | * { 16 | box-sizing: border-box; 17 | padding: 0; 18 | margin: 0; 19 | } 20 | 21 | a { 22 | color: inherit; 23 | text-decoration: none; 24 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/sections/Home/components/StatePOC/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 3 | 4 | import styles from "./Title.module.scss"; 5 | 6 | type TitleProps = { 7 | title: string; 8 | }; 9 | 10 | export const Title = ({ title }: TitleProps) => { 11 | return ( 12 | 13 |

{title}

14 |
15 | ); 16 | }; 17 | 18 | Title.displayName = "Title"; 19 | -------------------------------------------------------------------------------- /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/sections/Home/components/_StressTest/components/StateStress/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 3 | 4 | import styles from "./Title.module.scss"; 5 | 6 | type TitleProps = { 7 | title: string; 8 | }; 9 | 10 | export const Title = ({ title }: TitleProps) => { 11 | return ( 12 | 13 |

{title}

14 |
15 | ); 16 | }; 17 | 18 | Title.displayName = "Title"; 19 | -------------------------------------------------------------------------------- /src/sections/Home/components/ContextPOC/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 3 | 4 | import styles from "./Title.module.scss"; 5 | import { useSwitcher } from "../../providers/SwitcherProvider"; 6 | 7 | export const Title = () => { 8 | const { title } = useSwitcher(); 9 | 10 | return ( 11 | 12 |

{title}

13 |
14 | ); 15 | }; 16 | 17 | Title.displayName = "Title"; 18 | -------------------------------------------------------------------------------- /src/sections/Home/components/StateWithMemoPOC/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 3 | 4 | import styles from "./Title.module.scss"; 5 | import React from "react"; 6 | 7 | type TitleProps = { 8 | title: string; 9 | }; 10 | 11 | export const Title = React.memo(({ title }: TitleProps) => { 12 | return ( 13 | 14 |

{title}

15 |
16 | ); 17 | }); 18 | 19 | Title.displayName = "Title"; -------------------------------------------------------------------------------- /src/sections/Home/components/ZustandPOC/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 3 | 4 | import styles from "./Title.module.scss"; 5 | import { useSwitcherStore } from "../../stores/SwitcherStore"; 6 | 7 | export const Title = () => { 8 | const title = useSwitcherStore((state) => state.title); 9 | 10 | return ( 11 | 12 |

{title}

13 |
14 | ); 15 | }; 16 | 17 | Title.displayName = "Title"; 18 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/ZustandStress/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import styles from "./Title.module.scss"; 4 | import { useSwitcherStore } from "../../stores/useSwitcherStore"; 5 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 6 | 7 | export const Title = () => { 8 | const title = useSwitcherStore((state) => state.title); 9 | 10 | return ( 11 | 12 |

{title}

13 |
14 | ); 15 | }; 16 | 17 | Title.displayName = "Title"; 18 | -------------------------------------------------------------------------------- /src/sections/Home/components/ZustandPOC/ZustandPOC.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Container } from "./components/Container/Container"; 4 | import { Title } from "./components/Title/Title"; 5 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 6 | 7 | import styles from "./ZustandPOC.module.scss"; 8 | 9 | export const ZustandPOC = () => { 10 | return ( 11 | 12 |
13 | 14 | <Container /> 15 | </div> 16 | </RenderCount> 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateWithMemoStress/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 3 | 4 | import styles from "./Title.module.scss"; 5 | import React from "react"; 6 | 7 | type TitleProps = { 8 | title: string; 9 | }; 10 | 11 | export const Title = React.memo(({ title }: TitleProps) => { 12 | return ( 13 | <RenderCount className={styles.wrapper}> 14 | <h1 className={styles.title}>{title}</h1> 15 | </RenderCount> 16 | ); 17 | }); 18 | 19 | Title.displayName = "Title"; 20 | -------------------------------------------------------------------------------- /src/components/RenderCount/RenderCount.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: relative; 3 | } 4 | 5 | .textWrapper { 6 | position: absolute; 7 | top: 0; 8 | right: 0; 9 | 10 | display: flex; 11 | flex-direction: column; 12 | gap: 4px; 13 | 14 | width: fit-content; 15 | } 16 | 17 | .text { 18 | font-size: 10px; 19 | font-weight: bold; 20 | text-transform: uppercase; 21 | width: 100%; 22 | 23 | background-color: rgba(255, 255, 255, 0.15); 24 | color: #fff; 25 | padding: 4px 8px; 26 | border: 1px solid #000; 27 | border-radius: 2px; 28 | } -------------------------------------------------------------------------------- /src/sections/Home/components/StatePOC/components/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 2 | import { Switch } from "@/components/Switch/Switch"; 3 | 4 | import styles from "./Container.module.scss"; 5 | 6 | type ContainerProps = { 7 | onToggle: () => void; 8 | enabled: boolean; 9 | }; 10 | 11 | export const Container = ({ onToggle, enabled }: ContainerProps) => { 12 | return ( 13 | <RenderCount className={styles.wrapper}> 14 | <Switch onToggle={onToggle} enabled={enabled} /> 15 | </RenderCount> 16 | ); 17 | }; 18 | 19 | Container.displayName = "Container"; 20 | -------------------------------------------------------------------------------- /src/sections/Home/components/ContextPOC/components/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 2 | import { Switch } from "@/components/Switch/Switch"; 3 | 4 | import styles from "./Container.module.scss"; 5 | import { useSwitcher } from "../../providers/SwitcherProvider"; 6 | 7 | export const Container = () => { 8 | const { switcherValue, toggle } = useSwitcher(); 9 | 10 | return ( 11 | <RenderCount className={styles.wrapper}> 12 | <Switch onToggle={toggle} enabled={switcherValue} /> 13 | </RenderCount> 14 | ); 15 | }; 16 | 17 | Container.displayName = "Container"; 18 | -------------------------------------------------------------------------------- /src/sections/Home/components/StateWithMemoPOC/components/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 2 | import { Switch } from "@/components/Switch/Switch"; 3 | 4 | import styles from "./Container.module.scss"; 5 | 6 | type ContainerProps = { 7 | onToggle: () => void; 8 | enabled: boolean; 9 | }; 10 | 11 | export const Container = ({ onToggle, enabled }: ContainerProps) => { 12 | return ( 13 | <RenderCount className={styles.wrapper}> 14 | <Switch onToggle={onToggle} enabled={enabled} /> 15 | </RenderCount> 16 | ); 17 | }; 18 | 19 | Container.displayName = "Container"; 20 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateStress/components/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 2 | import { Switch } from "@/components/Switch/Switch"; 3 | 4 | import styles from "./Container.module.scss"; 5 | 6 | type ContainerProps = { 7 | onToggle: () => void; 8 | enabled: boolean; 9 | }; 10 | 11 | export const Container = ({ onToggle, enabled }: ContainerProps) => { 12 | return ( 13 | <RenderCount className={styles.wrapper}> 14 | <Switch onToggle={onToggle} enabled={enabled} /> 15 | </RenderCount> 16 | ); 17 | }; 18 | 19 | Container.displayName = "Container"; 20 | -------------------------------------------------------------------------------- /src/utils/classNames.ts: -------------------------------------------------------------------------------- 1 | export const classNames = ( 2 | ...classes: (string | undefined | Record<string, boolean>)[] 3 | ): string => { 4 | const concatedClasses = classes.reduce( 5 | (className, curr: string | undefined | Record<string, boolean>) => { 6 | if (!curr) return className; 7 | 8 | if (typeof curr === "string") { 9 | return `${className} ${curr}`; 10 | } 11 | 12 | return `${className} ${Object.entries(curr) 13 | .filter(([, value]) => !!value) 14 | .map(([key]) => key) 15 | .join(" ")}`; 16 | }, 17 | "" 18 | ) as string; 19 | 20 | return concatedClasses.trim(); 21 | }; 22 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateWithMemoStress/components/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 2 | import { Switch } from "@/components/Switch/Switch"; 3 | 4 | import styles from "./Container.module.scss"; 5 | 6 | type ContainerProps = { 7 | onToggle: () => void; 8 | enabled: boolean; 9 | }; 10 | 11 | export const Container = ({ onToggle, enabled }: ContainerProps) => { 12 | return ( 13 | <RenderCount className={styles.wrapper}> 14 | <Switch onToggle={onToggle} enabled={enabled} /> 15 | </RenderCount> 16 | ); 17 | }; 18 | 19 | Container.displayName = "Container"; 20 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/components/Switch/Switch.tsx: -------------------------------------------------------------------------------- 1 | import { classNames } from "@/utils/classNames"; 2 | import styles from "./Switch.module.scss"; 3 | 4 | type SwitchProps = { 5 | onToggle: () => void; 6 | enabled: boolean; 7 | }; 8 | 9 | export const Switch = ({ onToggle, enabled }: SwitchProps) => { 10 | const trackClassName = classNames(styles.track, { 11 | [styles.track__enabled]: enabled, 12 | }); 13 | 14 | const thumbClassName = classNames(styles.thumb, { 15 | [styles.thumb__enabled]: enabled, 16 | }); 17 | 18 | return ( 19 | <div className={trackClassName} onClick={onToggle}> 20 | <div className={thumbClassName} /> 21 | </div> 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/ZustandStress/components/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import { Switch } from "@/components/Switch/Switch"; 2 | 3 | import { useSwitcherStore } from "../../stores/useSwitcherStore"; 4 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 5 | 6 | export const Container = () => { 7 | const toggle = useSwitcherStore((state) => state.toggle); 8 | const switcherValue = useSwitcherStore((state) => state.switcherValue); 9 | 10 | return ( 11 | <RenderCount> 12 | <Switch onToggle={toggle} enabled={switcherValue} /> 13 | </RenderCount> 14 | ); 15 | }; 16 | 17 | Container.displayName = "Container"; 18 | -------------------------------------------------------------------------------- /src/components/Avatar/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | import styles from "./Avatar.module.scss"; 4 | import { classNames } from "@/utils/classNames"; 5 | 6 | type AvatarProps = { 7 | src: string; 8 | alt: string; 9 | priority?: boolean; 10 | className?: string; 11 | }; 12 | 13 | export const Avatar = ({ 14 | src, 15 | alt, 16 | priority, 17 | className: _className, 18 | }: AvatarProps) => { 19 | const className = classNames(styles.image, _className); 20 | 21 | return ( 22 | <Image 23 | className={className} 24 | src={src} 25 | alt={alt} 26 | width={256} 27 | height={256} 28 | priority={priority} 29 | /> 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/sections/Home/components/ContextPOC/ContextPOC.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Container } from "./components/Container/Container"; 4 | import { Title } from "./components/Title/Title"; 5 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 6 | 7 | import styles from "./ContextPOC.module.scss"; 8 | import { SwitcherProvider } from "./providers/SwitcherProvider"; 9 | 10 | export const ContextPOC = () => { 11 | return ( 12 | <SwitcherProvider> 13 | <RenderCount className={styles.wrapper}> 14 | <div className={styles.content}> 15 | <Title /> 16 | <Container /> 17 | </div> 18 | </RenderCount> 19 | </SwitcherProvider> 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/sections/Home/components/ZustandPOC/components/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 2 | import { Switch } from "@/components/Switch/Switch"; 3 | 4 | import styles from "./Container.module.scss"; 5 | import { useSwitcherStore } from "../../stores/SwitcherStore"; 6 | 7 | export const Container = () => { 8 | const toggle = useSwitcherStore(state => state.toggle); 9 | const switcherValue = useSwitcherStore(state => state.switcherValue); 10 | 11 | return ( 12 | <RenderCount className={styles.wrapper}> 13 | <Switch onToggle={toggle} enabled={switcherValue} /> 14 | </RenderCount> 15 | ); 16 | }; 17 | 18 | Container.displayName = "Container"; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/sections/Home/Home.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | margin: 0 auto; 3 | max-width: 1000px; 4 | width: 100%; 5 | 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | gap: 24px; 10 | 11 | overflow-x: hidden; 12 | 13 | padding: 40px 24px; 14 | 15 | p { 16 | width: 100%; 17 | line-height: 28px; 18 | } 19 | 20 | > h1 { 21 | margin-bottom: 40px; 22 | } 23 | 24 | h2 { 25 | margin-top: 40px; 26 | width: 100%; 27 | } 28 | 29 | code { 30 | background-color: #333; 31 | padding: 2px 4px; 32 | border-radius: 2px; 33 | } 34 | 35 | a { 36 | color: #5dced9; 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/components/Icon/Icon.tsx: -------------------------------------------------------------------------------- 1 | import { classNames } from "@/utils/classNames"; 2 | import styles from "./Icon.module.scss"; 3 | 4 | import { Github } from "./icons/Github"; 5 | import { Linkedin } from "./icons/Linkedin"; 6 | 7 | const icons = { 8 | linkedin: Linkedin, 9 | github: Github, 10 | }; 11 | 12 | export type IconName = keyof typeof icons; 13 | 14 | type IconProps = { 15 | name: IconName; 16 | className?: string; 17 | }; 18 | 19 | export const Icon = ({ name, className: _className }: IconProps) => { 20 | const IconComponent = icons[name]; 21 | 22 | const className = classNames(styles.icon, _className); 23 | 24 | return ( 25 | <figure className={className}> 26 | <IconComponent /> 27 | </figure> 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/Switch/Switch.module.scss: -------------------------------------------------------------------------------- 1 | .track { 2 | position: relative; 3 | background-color: #333; 4 | border-radius: 16px; 5 | height: 24px; 6 | width: 44px; 7 | 8 | transition: background-color .3s; 9 | will-change: background-color; 10 | } 11 | 12 | .thumb { 13 | border-radius: 100%; 14 | position: absolute; 15 | height: 20px; 16 | width: 20px; 17 | background-color: #111; 18 | 19 | top: 50%; 20 | transform: translateY(-50%); 21 | 22 | left: 2px; 23 | 24 | transition: transform .3s; 25 | will-change: transform; 26 | 27 | } 28 | 29 | .thumb__enabled { 30 | transform: translateY(-50%) translateX(100%); 31 | } 32 | 33 | .track__enabled { 34 | background-color: #eee; 35 | } -------------------------------------------------------------------------------- /src/sections/Home/components/StatePOC/StatePOC.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 300px; 4 | border: 1px solid #444; 5 | border-radius: 4px; 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 16px; 12 | 13 | padding: 40px; 14 | 15 | > div { 16 | top: 24px; 17 | right: 24px; 18 | } 19 | 20 | @media (max-width: 768px) { 21 | padding: 32px 24px; 22 | height: 200px; 23 | justify-content: flex-end; 24 | } 25 | } 26 | 27 | .content { 28 | width: 100%; 29 | max-width: 350px; 30 | 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | gap: 32px; 35 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/sections/Home/components/ContextPOC/ContextPOC.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 300px; 4 | border: 1px solid #444; 5 | border-radius: 4px; 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 16px; 12 | 13 | padding: 40px; 14 | 15 | > div { 16 | top: 24px; 17 | right: 24px; 18 | } 19 | 20 | @media (max-width: 768px) { 21 | padding: 32px 24px; 22 | height: 200px; 23 | justify-content: flex-end; 24 | } 25 | } 26 | 27 | .content { 28 | width: 100%; 29 | max-width: 350px; 30 | 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | gap: 32px; 35 | } -------------------------------------------------------------------------------- /src/sections/Home/components/ZustandPOC/ZustandPOC.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 300px; 4 | border: 1px solid #444; 5 | border-radius: 4px; 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 16px; 12 | 13 | padding: 40px; 14 | 15 | > div { 16 | top: 24px; 17 | right: 24px; 18 | } 19 | 20 | @media (max-width: 768px) { 21 | padding: 32px 24px; 22 | height: 200px; 23 | justify-content: flex-end; 24 | } 25 | } 26 | 27 | .content { 28 | width: 100%; 29 | max-width: 350px; 30 | 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | gap: 32px; 35 | } -------------------------------------------------------------------------------- /src/sections/Home/components/StateWithMemoPOC/StateWithMemoPOC.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 300px; 4 | border: 1px solid #444; 5 | border-radius: 4px; 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 16px; 12 | 13 | padding: 40px; 14 | 15 | > div { 16 | top: 24px; 17 | right: 24px; 18 | } 19 | 20 | @media (max-width: 768px) { 21 | padding: 32px 24px; 22 | height: 200px; 23 | justify-content: flex-end; 24 | } 25 | } 26 | 27 | .content { 28 | width: 100%; 29 | max-width: 350px; 30 | 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | gap: 32px; 35 | } -------------------------------------------------------------------------------- /src/components/CodeBlock/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import { Highlight, themes } from "prism-react-renderer"; 2 | 3 | import styles from "./CodeBlock.module.scss"; 4 | 5 | type CodeBlockProps = { 6 | code: string; 7 | }; 8 | 9 | export const CodeBlock = ({ code }: CodeBlockProps) => { 10 | return ( 11 | <Highlight theme={themes.vsDark} code={code} language="tsx"> 12 | {({ style, tokens, getLineProps, getTokenProps }) => ( 13 | <pre style={style} className={styles.code} translate="no"> 14 | {tokens.map((line, i) => ( 15 | <div key={i} {...getLineProps({ line })}> 16 | {line.map((token, key) => ( 17 | <span key={key} {...getTokenProps({ token })} /> 18 | ))} 19 | </div> 20 | ))} 21 | </pre> 22 | )} 23 | </Highlight> 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server' 2 | 3 | export default async function middleware(req: NextRequest) { 4 | const referer = req.headers.get('referer'); 5 | 6 | if (referer) { 7 | console.log('Usuário veio de:', referer); 8 | } else { 9 | console.log('Usuário acessou diretamente ou não há referer.'); 10 | } 11 | 12 | return NextResponse.next() 13 | } 14 | 15 | export const config = { 16 | matcher: [ 17 | /* 18 | * Match all request paths except for the ones starting with: 19 | * - api (API routes) 20 | * - _next/static (static files) 21 | * - _next/image (image optimization files) 22 | * - favicon.ico, sitemap.xml, robots.txt (metadata files) 23 | */ 24 | '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from "@/components/Avatar/Avatar"; 2 | 3 | import styles from "./Header.module.scss"; 4 | import { Icon } from "../Icon/Icon"; 5 | 6 | export const Header = () => { 7 | return ( 8 | <div className={styles.wrapper}> 9 | <a href="https://lucas.amaral.dev.br" target="_blank"> 10 | <Avatar 11 | src={"/lucas.jpeg"} 12 | alt="Profile image" 13 | priority 14 | className={styles.avatar} 15 | /> 16 | </a> 17 | 18 | <div className={styles.socialWrapper}> 19 | <a href="https://github.com/lucasca2/state-poc" target="_blank"> 20 | <Icon name="github" /> 21 | </a> 22 | <a href="https://www.linkedin.com/in/lucasca" target="_blank"> 23 | <Icon name="linkedin" /> 24 | </a> 25 | </div> 26 | </div> 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/sections/Home/components/StatePOC/StatePOC.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useCallback, useState } from "react"; 4 | import { Container } from "./components/Container/Container"; 5 | import { Title } from "./components/Title/Title"; 6 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 7 | 8 | import styles from "./StatePOC.module.scss"; 9 | 10 | export const StatePOC = () => { 11 | const [switcherValue, setSwitcherValue] = useState(false); 12 | const [title] = useState("State"); 13 | 14 | const handleToggle = useCallback(() => { 15 | setSwitcherValue((prev) => !prev); 16 | }, []); 17 | 18 | return ( 19 | <RenderCount className={styles.wrapper}> 20 | <div className={styles.content}> 21 | <Title title={title} /> 22 | <Container onToggle={handleToggle} enabled={switcherValue} /> 23 | </div> 24 | </RenderCount> 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/sections/Home/components/StateWithMemoPOC/StateWithMemoPOC.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useCallback, useState } from "react"; 4 | import { Container } from "./components/Container/Container"; 5 | import { Title } from "./components/Title/Title"; 6 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 7 | 8 | import styles from "./StateWithMemoPOC.module.scss"; 9 | 10 | export const StateWithMemoPOC = () => { 11 | const [switcherValue, setSwitcherValue] = useState(false); 12 | const [title] = useState("State"); 13 | 14 | const handleToggle = useCallback(() => { 15 | setSwitcherValue((prev) => !prev); 16 | }, []); 17 | 18 | return ( 19 | <RenderCount className={styles.wrapper}> 20 | <div className={styles.content}> 21 | <Title title={title} /> 22 | <Container onToggle={handleToggle} enabled={switcherValue} /> 23 | </div> 24 | </RenderCount> 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/StressGroup.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 300px; 4 | border: 1px solid #444; 5 | border-radius: 4px; 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 16px; 12 | 13 | padding: 40px; 14 | 15 | > div { 16 | top: 24px; 17 | right: 24px; 18 | } 19 | 20 | @media (max-width: 768px) { 21 | padding: 32px 24px; 22 | height: 200px; 23 | justify-content: flex-end; 24 | } 25 | } 26 | 27 | .button { 28 | border: none; 29 | background-color: #222; 30 | color: #fff; 31 | width: 100%; 32 | max-width: 350px; 33 | padding: 0 24px; 34 | height: 44px; 35 | font-size: 16px; 36 | border-radius: 4px; 37 | 38 | cursor: pointer; 39 | transition: opacity .3s; 40 | will-change: opacity; 41 | 42 | &:hover { 43 | opacity: .8; 44 | } 45 | } -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg> -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateStress/StateStress.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 300px; 4 | border: 1px solid #444; 5 | border-radius: 4px; 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 16px; 12 | 13 | padding: 40px; 14 | 15 | > div { 16 | top: 24px; 17 | right: 24px; 18 | } 19 | 20 | @media (max-width: 768px) { 21 | height: fit-content; 22 | padding: 120px 24px 32px 24px; 23 | justify-content: flex-end; 24 | } 25 | } 26 | 27 | .content { 28 | width: 100%; 29 | max-width: 350px; 30 | 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | gap: 32px; 35 | } 36 | 37 | .array { 38 | display: flex; 39 | flex-direction: column; 40 | gap: 8px; 41 | max-height: 100px; 42 | overflow: auto; 43 | width: 100%; 44 | 45 | > div { 46 | width: 100%; 47 | } 48 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/ZustandStress/ZustandStress.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 300px; 4 | border: 1px solid #444; 5 | border-radius: 4px; 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 16px; 12 | 13 | padding: 40px; 14 | 15 | > div { 16 | top: 24px; 17 | right: 24px; 18 | } 19 | 20 | @media (max-width: 768px) { 21 | height: fit-content; 22 | padding: 120px 24px 32px 24px; 23 | justify-content: flex-end; 24 | } 25 | } 26 | 27 | .content { 28 | width: 100%; 29 | max-width: 350px; 30 | 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | gap: 32px; 35 | } 36 | 37 | .array { 38 | display: flex; 39 | flex-direction: column; 40 | gap: 8px; 41 | max-height: 100px; 42 | overflow: auto; 43 | width: 100%; 44 | 45 | > div { 46 | width: 100%; 47 | } 48 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateWithMemoStress/StateWithMemoStress.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 300px; 4 | border: 1px solid #444; 5 | border-radius: 4px; 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 16px; 12 | 13 | padding: 40px; 14 | 15 | > div { 16 | top: 24px; 17 | right: 24px; 18 | } 19 | 20 | @media (max-width: 768px) { 21 | height: fit-content; 22 | padding: 120px 24px 32px 24px; 23 | justify-content: flex-end; 24 | } 25 | } 26 | 27 | .content { 28 | width: 100%; 29 | max-width: 350px; 30 | 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | gap: 32px; 35 | } 36 | 37 | .array { 38 | display: flex; 39 | flex-direction: column; 40 | gap: 8px; 41 | max-height: 100px; 42 | overflow: auto; 43 | width: 100%; 44 | 45 | > div { 46 | width: 100%; 47 | } 48 | } -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/ZustandStress/ZustandStress.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Container } from "./components/Container/Container"; 4 | import { Title } from "./components/Title/Title"; 5 | 6 | import styles from "./ZustandStress.module.scss"; 7 | import { Child } from "../../shared/Child/Child"; 8 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 9 | 10 | type ZustandStressProps = { 11 | countOfChildren: number; 12 | }; 13 | 14 | export const ZustandStress = ({ countOfChildren }: ZustandStressProps) => { 15 | return ( 16 | <RenderCount className={styles.wrapper} id="Zustand"> 17 | <div className={styles.content}> 18 | <Title /> 19 | <Container /> 20 | <div className={styles.array}> 21 | <span>List with {countOfChildren} items</span> 22 | {[...Array(countOfChildren)].map((_, i) => ( 23 | <RenderCount key={i}> 24 | <Child value={(i + 1).toString()} /> 25 | </RenderCount> 26 | ))} 27 | </div> 28 | </div> 29 | </RenderCount> 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/StressGroup.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { StateStress } from "./components/StateStress/StateStress"; 5 | import { StateWithMemoStress } from "./components/StateWithMemoStress/StateWithMemoStress"; 6 | import { ZustandStress } from "./components/ZustandStress/ZustandStress"; 7 | 8 | import styles from "./StressGroup.module.scss"; 9 | 10 | type StressGroupProps = { 11 | countOfChildren: number; 12 | }; 13 | 14 | export const StressGroup = ({ countOfChildren }: StressGroupProps) => { 15 | const [shouldShow, setShouldShow] = useState(false); 16 | 17 | 18 | if (shouldShow) { 19 | return ( 20 | <> 21 | <StateStress countOfChildren={countOfChildren} /> 22 | <StateWithMemoStress countOfChildren={countOfChildren} /> 23 | <ZustandStress countOfChildren={countOfChildren} /> 24 | </> 25 | ); 26 | } 27 | 28 | return ( 29 | <div className={styles.wrapper}> 30 | <button className={styles.button} onClick={() => setShouldShow(true)}> 31 | Iniciar testes 32 | </button> 33 | </div> 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/sections/Home/components/ContextPOC/providers/SwitcherProvider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useState } from "react"; 2 | 3 | type SwitcherContextType = { 4 | switcherValue: boolean; 5 | toggle: () => void; 6 | title: string; 7 | }; 8 | 9 | export const SwitcherContext = createContext<SwitcherContextType>({} as SwitcherContextType); 10 | 11 | type SwitcherProviderProps = { 12 | children: React.ReactNode; 13 | }; 14 | 15 | const SwitcherProvider = ({ children }: SwitcherProviderProps) => { 16 | const [switcherValue, setSwitcherValue] = useState(false); 17 | const [title] = useState("Context"); 18 | 19 | const handleToggle = () => { 20 | setSwitcherValue(!switcherValue); 21 | }; 22 | 23 | return ( 24 | <SwitcherContext.Provider value={{ switcherValue, toggle: handleToggle, title }}> 25 | {children} 26 | </SwitcherContext.Provider> 27 | ); 28 | }; 29 | 30 | const useSwitcher = () => { 31 | const context = useContext(SwitcherContext); 32 | 33 | if (!context) { 34 | throw new Error("useSwitcher must be used within a SwitcherProvider"); 35 | } 36 | 37 | return context; 38 | }; 39 | 40 | export { useSwitcher, SwitcherProvider }; -------------------------------------------------------------------------------- /src/components/Icon/icons/Linkedin.tsx: -------------------------------------------------------------------------------- 1 | export const Linkedin = () => { 2 | return ( 3 | <svg 4 | width="24" 5 | height="24" 6 | viewBox="0 0 24 24" 7 | fill="none" 8 | xmlns="http://www.w3.org/2000/svg" 9 | > 10 | <g clipPath="url(#clip0_11_590)"> 11 | <path 12 | d="M22.2234 0H1.77187C0.792187 0 0 0.773438 0 1.72969V22.2656C0 23.2219 0.792187 24 1.77187 24H22.2234C23.2031 24 24 23.2219 24 22.2703V1.72969C24 0.773438 23.2031 0 22.2234 0ZM7.12031 20.4516H3.55781V8.99531H7.12031V20.4516ZM5.33906 7.43438C4.19531 7.43438 3.27188 6.51094 3.27188 5.37187C3.27188 4.23281 4.19531 3.30937 5.33906 3.30937C6.47813 3.30937 7.40156 4.23281 7.40156 5.37187C7.40156 6.50625 6.47813 7.43438 5.33906 7.43438ZM20.4516 20.4516H16.8937V14.8828C16.8937 13.5562 16.8703 11.8453 15.0422 11.8453C13.1906 11.8453 12.9094 13.2937 12.9094 14.7891V20.4516H9.35625V8.99531H12.7687V10.5609H12.8156C13.2891 9.66094 14.4516 8.70938 16.1813 8.70938C19.7859 8.70938 20.4516 11.0813 20.4516 14.1656V20.4516Z" 13 | fill="currentColor" 14 | /> 15 | </g> 16 | <defs> 17 | <clipPath id="clip0_11_590"> 18 | <rect width="24" height="24" fill="white" /> 19 | </clipPath> 20 | </defs> 21 | </svg> 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg> -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateStress/StateStress.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useCallback, useState } from "react"; 4 | import { Container } from "./components/Container/Container"; 5 | import { Title } from "./components/Title/Title"; 6 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 7 | 8 | import styles from "./StateStress.module.scss"; 9 | import { Child } from "../../shared/Child/Child"; 10 | 11 | type StateStressProps = { 12 | countOfChildren: number; 13 | }; 14 | 15 | export const StateStress = ({ countOfChildren }: StateStressProps) => { 16 | const [switcherValue, setSwitcherValue] = useState(false); 17 | const [title] = useState("Use State"); 18 | 19 | const handleToggle = useCallback(() => { 20 | setSwitcherValue((prev) => !prev); 21 | }, []); 22 | 23 | return ( 24 | <RenderCount className={styles.wrapper} id="State"> 25 | <div className={styles.content}> 26 | <Title title={title} /> 27 | <Container onToggle={handleToggle} enabled={switcherValue} /> 28 | <div className={styles.array}> 29 | <span>List with {countOfChildren} items</span> 30 | {[...Array(countOfChildren)].map((_, i) => ( 31 | <RenderCount key={i}> 32 | <Child value={(i + 1).toString()} /> 33 | </RenderCount> 34 | ))} 35 | </div> 36 | </div> 37 | </RenderCount> 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/sections/Home/components/_StressTest/components/StateWithMemoStress/StateWithMemoStress.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useCallback, useMemo, useState } from "react"; 4 | import { Container } from "./components/Container/Container"; 5 | import { Title } from "./components/Title/Title"; 6 | import { RenderCount } from "@/components/RenderCount/RenderCount"; 7 | 8 | import styles from "./StateWithMemoStress.module.scss"; 9 | import { MemoizedChild } from "../../shared/MemoizedChild/MemoizedChild"; 10 | 11 | type StateWithMemoStressProps = { 12 | countOfChildren: number; 13 | }; 14 | 15 | export const StateWithMemoStress = ({ 16 | countOfChildren, 17 | }: StateWithMemoStressProps) => { 18 | const [switcherValue, setSwitcherValue] = useState(false); 19 | const [title] = useState("Use State & Memo"); 20 | 21 | const handleToggle = useCallback(() => { 22 | setSwitcherValue((prev) => !prev); 23 | }, []); 24 | 25 | const array = useMemo(() => { 26 | return [...Array(countOfChildren)].map((_, i) => ( 27 | <MemoizedChild key={i} value={(i + 1).toString()} /> 28 | )); 29 | }, [countOfChildren]); 30 | 31 | return ( 32 | <RenderCount className={styles.wrapper} id="State-Memo"> 33 | <div className={styles.content}> 34 | <Title title={title} /> 35 | <Container onToggle={handleToggle} enabled={switcherValue} /> 36 | <div className={styles.array}> 37 | <span>List with {countOfChildren} items</span> 38 | {array} 39 | </div> 40 | </div> 41 | </RenderCount> 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/components/Icon/icons/Github.tsx: -------------------------------------------------------------------------------- 1 | export const Github = () => { 2 | return ( 3 | <svg 4 | width="24" 5 | height="24" 6 | viewBox="0 0 24 24" 7 | fill="none" 8 | xmlns="http://www.w3.org/2000/svg" 9 | > 10 | <g clipPath="url(#clip0_11_168)"> 11 | <path 12 | fillRule="evenodd" 13 | clipRule="evenodd" 14 | d="M12.0099 0C5.36875 0 0 5.40833 0 12.0992C0 17.4475 3.43994 21.9748 8.21205 23.5771C8.80869 23.6976 9.02724 23.3168 9.02724 22.9965C9.02724 22.716 9.00757 21.7545 9.00757 20.7527C5.6667 21.474 4.97099 19.3104 4.97099 19.3104C4.43409 17.9082 3.63858 17.5478 3.63858 17.5478C2.54511 16.8066 3.71823 16.8066 3.71823 16.8066C4.93117 16.8868 5.56763 18.0486 5.56763 18.0486C6.64118 19.8913 8.37111 19.3707 9.06706 19.0501C9.16638 18.2688 9.48473 17.728 9.82275 17.4276C7.15817 17.1471 4.35469 16.1055 4.35469 11.458C4.35469 10.1359 4.8316 9.05428 5.58729 8.21304C5.46807 7.91263 5.0504 6.67043 5.70677 5.00787C5.70677 5.00787 6.72083 4.6873 9.00732 6.24981C9.98625 5.98497 10.9958 5.85024 12.0099 5.84911C13.024 5.84911 14.0577 5.98948 15.0123 6.24981C17.299 4.6873 18.3131 5.00787 18.3131 5.00787C18.9695 6.67043 18.5515 7.91263 18.4323 8.21304C19.2079 9.05428 19.6652 10.1359 19.6652 11.458C19.6652 16.1055 16.8617 17.1269 14.1772 17.4276C14.6148 17.8081 14.9924 18.5292 14.9924 19.6711C14.9924 21.2936 14.9727 22.5957 14.9727 22.9962C14.9727 23.3168 15.1915 23.6976 15.7879 23.5774C20.56 21.9745 23.9999 17.4475 23.9999 12.0992C24.0196 5.40833 18.6312 0 12.0099 0Z" 15 | fill="currentColor" 16 | /> 17 | </g> 18 | <defs> 19 | <clipPath id="clip0_11_168"> 20 | <rect width="24" height="24" fill="white" /> 21 | </clipPath> 22 | </defs> 23 | </svg> 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/RenderCount/RenderCount.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useRef, useEffect, Profiler } from "react"; 3 | import styles from "./RenderCount.module.scss"; 4 | import { classNames } from "@/utils/classNames"; 5 | 6 | type RenderCountProps = { 7 | id?: string; 8 | children?: React.ReactNode; 9 | className?: string; 10 | }; 11 | 12 | export const RenderCount = ({ 13 | id, 14 | children, 15 | className: _className, 16 | }: RenderCountProps) => { 17 | const isClient = useRef(false); 18 | const renderCount = useRef(1); 19 | const renderTimeSpan = useRef<HTMLSpanElement>(null); 20 | 21 | useEffect(() => { 22 | if (!isClient.current) { 23 | isClient.current = true; 24 | } 25 | }, []); 26 | 27 | if (isClient.current) { 28 | renderCount.current += 1; 29 | } 30 | 31 | const className = classNames(styles.wrapper, _className); 32 | 33 | if (id) { 34 | return ( 35 | <Profiler 36 | id={id} 37 | onRender={(id, phase, actualDuration) => { 38 | if (renderTimeSpan.current) { 39 | renderTimeSpan.current.textContent = `${phase}: ${actualDuration.toFixed( 40 | 2 41 | )} ms`; 42 | } 43 | }} 44 | > 45 | <div className={className} translate="no"> 46 | {children} 47 | <div className={styles.textWrapper}> 48 | <span className={styles.text} ref={renderTimeSpan}> 49 | Mounting 50 | </span> 51 | <span className={styles.text}> 52 | Rendered: {renderCount.current} times 53 | </span> 54 | </div> 55 | </div> 56 | </Profiler> 57 | ); 58 | } 59 | 60 | return ( 61 | <div className={className} translate="no"> 62 | {children} 63 | <div className={styles.textWrapper}> 64 | <span className={styles.text}> 65 | Rendered: {renderCount.current} times 66 | </span> 67 | </div> 68 | </div> 69 | ); 70 | }; 71 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/sections/Home/constants.ts: -------------------------------------------------------------------------------- 1 | export const statePOC = `export const App = () => { 2 | const [switcherValue, setSwitcherValue] = useState(false); 3 | const [title] = useState("State"); 4 | 5 | 6 | const handleToggle = useCallback(() => { 7 | setSwitcherValue(prev => !prev); 8 | }, []); 9 | 10 | return ( 11 | <div> 12 | <Title title={title} /> 13 | <Container onToggle={handleToggle} enabled={switcherValue} /> 14 | </div> 15 | ); 16 | };`; 17 | 18 | export const statePOCTitleWithMemo = `export const Title = React.memo(({ title }: TitleProps) => { 19 | return ( 20 | <h1>{title}</h1> 21 | ); 22 | }); 23 | 24 | Title.displayName = "Title";` 25 | 26 | export const contextPOC = `export const App = () => { 27 | return ( 28 | <SwitcherProvider> 29 | <div> 30 | <Title /> 31 | <Container /> 32 | </div> 33 | </SwitcherProvider> 34 | ); 35 | };`; 36 | 37 | export const contextPOCProvider = `const SwitcherProvider = ({ children }: SwitcherProviderProps) => { 38 | const [switcherValue, setSwitcherValue] = useState(false); 39 | const [title] = useState("Context"); 40 | 41 | const handleToggle = () => { 42 | setSwitcherValue(!switcherValue); 43 | }; 44 | 45 | return ( 46 | <SwitcherContext.Provider value={{ switcherValue, toggle: handleToggle, title }}> 47 | {children} 48 | </SwitcherContext.Provider> 49 | ); 50 | }; 51 | 52 | const useSwitcher = () => { 53 | const context = useContext(SwitcherContext); 54 | 55 | return context; 56 | };`; 57 | 58 | export const contextPOCTitle = `export const Title = () => { 59 | const { title } = useSwitcher(); 60 | 61 | return ( 62 | <h1>{title}</h1> 63 | ); 64 | };`; 65 | 66 | export const contextPOCSwitcher = `export const Container = () => { 67 | const { switcherValue, toggle } = useSwitcher(); 68 | 69 | return ( 70 | <Switch onToggle={toggle} enabled={switcherValue} /> 71 | ); 72 | };`; 73 | 74 | export const zustandPOCStore = `export const useSwitcherStore = create<SwitcherStore>((set) => ({ 75 | title: "Zustand", 76 | switcherValue: false, 77 | toggle: () => set((state) => ({ switcherValue: !state.switcherValue })), 78 | }))` 79 | 80 | export const zustandPOC = `export const App = () => { 81 | return ( 82 | <div> 83 | <Title /> 84 | <Container /> 85 | </div> 86 | ); 87 | }` 88 | 89 | export const zustandPOCTitle = `export const Title = () => { 90 | const title = useSwitcherStore((state) => state.title); 91 | 92 | return ( 93 | <h1>{title}</h1> 94 | ); 95 | };`; 96 | 97 | export const zustandPOCSwitcher = `export const Container = () => { 98 | const toggle = useSwitcherStore(state => state.toggle); 99 | const switcherValue = useSwitcherStore(state => state.switcherValue); 100 | 101 | return ( 102 | <Switch onToggle={toggle} enabled={switcherValue} /> 103 | ); 104 | };`; -------------------------------------------------------------------------------- /src/sections/Home/Home.tsx: -------------------------------------------------------------------------------- 1 | import { ContextPOC } from "./components/ContextPOC/ContextPOC"; 2 | import { StatePOC } from "./components/StatePOC/StatePOC"; 3 | 4 | import styles from "./Home.module.scss"; 5 | import { CodeBlock } from "@/components/CodeBlock/CodeBlock"; 6 | import { StateWithMemoPOC } from "./components/StateWithMemoPOC/StateWithMemoPOC"; 7 | import { 8 | contextPOC, 9 | contextPOCProvider, 10 | contextPOCSwitcher, 11 | contextPOCTitle, 12 | statePOC, 13 | statePOCTitleWithMemo, 14 | zustandPOC, 15 | zustandPOCStore, 16 | zustandPOCSwitcher, 17 | zustandPOCTitle, 18 | } from "./constants"; 19 | import { ZustandPOC } from "./components/ZustandPOC/ZustandPOC"; 20 | import React from "react"; 21 | import { StressGroup } from "./components/_StressTest/StressGroup"; 22 | import { Header } from "@/components/Header/Header"; 23 | import { Footer } from "@/components/Footer/Footer"; 24 | 25 | export const Home = () => { 26 | return ( 27 | <div className={styles.wrapper}> 28 | <Header /> 29 | <h1> 30 | Gerenciamento de estado no <code>React.js</code> 31 | </h1> 32 | <p> 33 | Primeiramente, vamos pensar em um cenário simples onde temos que 34 | gerenciar um estado que será compartilhado entre dois componentes. 35 | <br /> 36 | Então teremos um componente pai, que irá renderizar dois componentes 37 | filhos, e esses componentes filhos precisam compartilhar o mesmo estado. 38 | <br /> 39 | Um deles vai receber apenas um texto (que poderia vir de uma API) e o 40 | outro vai ser um <code>Switcher</code> que vai servir apenas pra 41 | conseguir simular essa mudança de estado. 42 | </p> 43 | 44 | <h2> 45 | Usando <code>useState</code>; 46 | </h2> 47 | <p> 48 | Acredito que a primeira coisa que vem à mente é usar o{" "} 49 | <code>useState</code> 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 | <br /> 53 | Algo mais ou menos assim: 54 | </p> 55 | <CodeBlock code={statePOC} /> 56 | <StatePOC /> 57 | <p> 58 | Se notar nessa abordagem, cada vez que o state do{" "} 59 | <code>switcherValue</code> muda, todos os componentes são 60 | re-renderizados novamente, até mesmo o <code>Title</code> que não tem 61 | nada a ver com isso! 62 | </p> 63 | <p> 64 | Isso acontece por que toda vez que um estado muda, o componente que o 65 | contém é re-renderizado, e como o <code>App</code> é o componente pai de 66 | todos os outros, todos eles são re-renderizados juntos. 67 | </p> 68 | <p> 69 | Existe uma forma de otimizar isso utilizando o <code>React.memo</code>, 70 | que basicamente faz com que um componente só seja re-renderizado se 71 | alguma propriedade que ele recebe for alterada. 72 | <br /> 73 | Algo mais ou menos assim: 74 | </p> 75 | <CodeBlock code={statePOCTitleWithMemo} /> 76 | <p> 77 | Com essa simples abordagem, ele já resolve o problema de re-renderizar o{" "} 78 | <code>Title</code> toda vez que o <code>switcherValue</code> muda. 79 | </p> 80 | <StateWithMemoPOC /> 81 | <p> 82 | Porém note que o <code>App</code> continua re-renderizando toda vez que 83 | o <code>switcherValue</code> muda... Isso é um problema? não 84 | necessariamente, por que nesse caso o unico componente que vai ter 85 | impacto de fato é o <code>Container</code>, que é o esperado. O{" "} 86 | <code>App</code> é re-renderizado, mas ele só vai causar o re-render do{" "} 87 | <code>Container</code> que realmente deve ser re-renderizado, já que seu 88 | estado mudou, enquanto o <code>Title</code> vai continuar lá intacto por 89 | conta do <code>React.memo</code>. 90 | </p> 91 | 92 | <h2> 93 | Usando <code>createContext</code>; 94 | </h2> 95 | <p> 96 | Um problema que temos quando começamos a usar a abordagem de{" "} 97 | <code>useState</code> é 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 | </p> 101 | <p> 102 | E então é que surge a brilhante ideia de resolver isso utilizando um{" "} 103 | <code>Context</code> do React, que basicamente é um objeto que vai ser 104 | compartilhado entre todos os componentes que estão dentro dele. 105 | </p> 106 | <p> 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 | </p> 111 | <p> 112 | Vamos pegar e passar toda essa lógica do state e colocar dentro de um{" "} 113 | <code>Context</code> então pra ver como ele se comporta. 114 | </p> 115 | <CodeBlock code={contextPOCProvider} /> 116 | <CodeBlock code={contextPOC} /> 117 | <p> 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 | <br /> 121 | Dentro de cada componente filho a gente simplesmente chamaria o contexto 122 | e pegaria o estado que a gente precisa. 123 | </p> 124 | <CodeBlock code={contextPOCTitle} /> 125 | <CodeBlock code={contextPOCSwitcher} /> 126 | <p> 127 | Muito mais organizado, mas vamos ver na prática como isso se comporta? 128 | </p> 129 | <ContextPOC /> 130 | <p> 131 | Note que o <code>App</code> realmente parou de re-renderizar, ok! Mas o{" "} 132 | <code>Title</code> está re-renderizando toda vez que o{" "} 133 | <code>switcherValue</code> muda, e isso é um problema. 134 | </p> 135 | <p> 136 | Isso acontece por que o <code>React</code> 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á <code>React.memo</code>{" "} 139 | que resolva, por que nesse caso não tem nenhuma propriedade que muda, o 140 | que muda é o estado do contexto. 141 | </p> 142 | <p>Como resolver isso então? Bom, a resposta é: Depende...</p> 143 | <p> 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 | <br /> 150 | Então geralmente o <code>Context</code> é 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 | </p> 154 | <h2> 155 | Usando <code>Zustand</code>; 156 | </h2> 157 | <p> 158 | Se usar <code>useState</code>, fica muito verboso, se usar{" "} 159 | <code>createContext</code>, não fica performático, o que usar então? 160 | </p> 161 | <p> 162 | Nesses casos, a gente pode optar por usar um gerenciador de estados mais 163 | robusto, como por exemplo um Redux, Zustand, etc...{" "} 164 | </p> 165 | <p> 166 | A diferença entre esses gerenciadores de estados robustos e o{" "} 167 | <code>Context</code> é que a gente consegue observar apenas um pedaço do 168 | estado invés de sempre observar o estado inteiro. 169 | </p> 170 | <p>Nesse exemplo vou usar o Zustand pela facilidade da implementação:</p> 171 | <CodeBlock code={zustandPOCStore} /> 172 | <CodeBlock code={zustandPOC} /> 173 | <p> 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 | </p> 177 | <CodeBlock code={zustandPOCTitle} /> 178 | <CodeBlock code={zustandPOCSwitcher} /> 179 | <p> 180 | Dessa forma, o componente <code>Title</code> vai re-renderizar apenas se 181 | o estado <code>title</code> for alterado, por que agora ele não está 182 | mais observando o restante dos estados 183 | </p> 184 | <p> 185 | O mesmo vale pro <code>Container</code>, se por alguma razão o estado{" "} 186 | <code>title</code> fosse alterado, isso não o afetaria em nada. 187 | </p> 188 | <p>Mas vamos ver na prática como isso fica:</p> 189 | <ZustandPOC /> 190 | <p> 191 | Usando um gerenciador mais robusto, a gente até consegue evitar o uso do{" "} 192 | <code>React.memo</code> 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 <code>useState</code>) 195 | </p> 196 | <h2>Considerações finais</h2> 197 | <p> 198 | Na maioria das vezes, um simples <code>useState</code> é 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 | </p> 203 | <p> 204 | Porém usar <code>React.memo</code> 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 | </p> 208 | <p> 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 | </p> 213 | <p> 214 | Mas que custo é esse? falar é fácil, então vamos fazer um teste de 215 | stress e ver na prática! 216 | </p> 217 | <h2>Componentes pequenos</h2> 218 | <StressGroup countOfChildren={20} /> 219 | 220 | <h2>Componentes gigantes</h2> 221 | <StressGroup countOfChildren={10000} /> 222 | 223 | <p> 224 | Em resumo, a melhor abordagem vai depender do seu caso de uso, se você 225 | tem um estado simples, use um <code>useState</code>, 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 <code>useState</code> de uma 228 | forma mais limpa, use um <code>Context</code>. 229 | </p> 230 | <p> 231 | Na maioria das vezes o <code>useState</code> é 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 | </p> 235 | <p> 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 | </p> 239 | <p> 240 | Fique a vontade pra instalar o{" "} 241 | <a href="https://github.com/lucasca2/state-poc">repositório</a> e fazer mais testes por 242 | conta própria! 243 | </p> 244 | <Footer /> 245 | </div> 246 | ); 247 | }; 248 | --------------------------------------------------------------------------------