Projeto de Timer com Nextjs 13 App Router, React, Typescript e Tailwindcss.
Projeto de Pomodoro Timer com Nextjs 13 App Router, React, Typescript e Tailwindcss.
Sobre • Vitrine Dev • Tecnologias • Instalações • Funcionalidades • Deploy • Good habits • Autor • Licença
💻 Sobre o projeto
🚀 Olá, sou Livio Alvarenga, um desenvolvedor Full Stack. Hoje, tenho o prazer de apresentar a vocês uma poderosa ferramenta que desenvolvi - o Pomodoro Timer. Utilizando tecnologias modernas como NodeJs, TypeScript, JavaScript, NextJs e React, esse aplicativo oferece uma maneira eficiente de gerenciar suas tarefas e tempo.
Com o Pomodoro Timer, você pode inserir tarefas, definir a duração e acompanhar o progresso em tempo real. Projetado com uma interface intuitiva, o aplicativo permite que você inicie, interrompa e visualize o tempo restante das tarefas diretamente no timer ou na barra de título. Cada tarefa, uma vez inserida, pode ser acompanhada em termos de status - concluída, em andamento ou interrompida. Tudo isso com a conveniência de ter seus dados armazenados no localStorage para referência futura.
Também incorporei uma página de histórico do timer, onde você pode visualizar detalhes de todas as suas tarefas, incluindo duração, início e status.
Esse repositório é uma oportunidade para eu compartilhar com outros desenvolvedores o conhecimento e as habilidades que adquiri. As tecnologias que escolhi para este projeto, além das já mencionadas, incluem React Hook Form, Tailwind, ZodJs, ESLint e Prettier.
Eu defini algumas regras de negócio que devem ser observadas: a tarefa e a duração devem ser definidas antes de iniciar o timer, e uma tarefa em andamento deve ser interrompida antes de começar uma nova.
Além disso, aproveitei este projeto para discutir tópicos técnicos importantes, como Formulários Controlados versus Não Controlados, o uso do hook useEffect, a evitação do Prop Drilling, as diferenças entre useState e useReducer, e o uso da Context API.
Convido vocês a conferirem o aplicativo em funcionamento no seguinte link, hospedado pela Vercel: Pomodoro Timer. Explore o código, aprenda com ele e, se sentir vontade, contribua também. Seja bem-vindo ao meu universo de codificação!
📺 Vitrine Dev
🪧 Vitrine.Dev | |
---|---|
✨ Nome | Projeto de Timer com Nextjs 13 App Router, React, Typescript e Tailwindcss. |
🏷️ Tecnologias | NodeJs, TypeScript, JavaScript, Typescript, Nextjs, React, React Hook Form, Tailwind, ZodJs, EsLint e prettier. |
🛠 Tecnologias
As seguintes ferramentas foram usadas na construção do projeto
⚙️ Instalações
Criando projeto NextJs
# Create project nodejs with npm and -y to accept all default options
npx create-next-app@latest
# What is your project named? project-Name
# Would you like to use TypeScript with this project? Yes
# Would you like to use ESLint with this project? Yes
# Would you like to use Tailwind CSS with this project? Yes
# Would you like to use `src/` directory with this project? Yes
# Use App Router (recommended)? Yes
# Would you like to customize the default import alias? No
// Create scripts in package.json
"scripts": {
"dev": "next dev", // Start development server
"build": "next build", // Create production build
"start": "next start", // Start production server
"lint": "next lint" // Run ESLint
},
Create .nvmrc
file with version of NodeJs to use in project
Configurando ESlint and Prettier
npm install -D @rocketseat/eslint-config # Install Rocketseat ESLint config
npm install -D prettier-plugin-tailwindcss # Install prettier plugin to use TailwindCSS
Edit .eslintrc.json
file with extends Rocketseat ESLint config
{
"extends": ["next/core-web-vitals", "@rocketseat/eslint-config/react"]
}
Create .eslintignore
file with all ESLint ignore files
Create prettier.config.js
file with plugin prettier-plugin-tailwindcss to use Prettier to format classnames of TailwindCSS
module.exports = {
plugins: [require("prettier-plugin-tailwindcss")],
};
TypeScript architecture
...
Others libraries
npm install zod # Install zod to use types in NodeJs and validate data
⚙️ Funcionalidades
RF - Requisitos Funcionais
- Deve ser possível inserir uma tarefa e tempo de duração da tarefa;
- Deve ser possível iniciar o timer;
- Deve ser possível interromper o timer;
- Deve ser possível ver o histórico de tarefas;
- Deve ser possível ver o tempo restante da tarefa no timer e barra de titulo;
- Deve ser possível armazenar os dados no localStorage;
- Deve ser possível ver o status de cada tarefa como concluída, em andamento ou interrompida;
RN - Regras de Negócio
- O usuário não pode não pode começar uma tarefa sem inserir o nome da tarefa e o tempo de duração;
- O usuário não pode começar outra tarefa sem interromper a tarefa atual;
RNF - Requisitos Não Funcionais
- Uso de Zod para validação de dados de entrada;
- Uso de Eslint para padronização de código;
- Uso de Prettier para padronização de código;
- Uso de TailwindCSS para estilização;
- Uso de NextJs para SSR;
- Uso de TypeScript para tipagem estática;
- Uso de NextJs com App Router para rotas;
- Uso Context API para compartilhar dados entre componentes;
- Uso de CLSX para gerar classnames dinâmicos;
- Uso de date-fns para manipulação de datas;
- Uso de immer para manipulação de estados imutáveis;
- Uso de lucide-react para ícones;
- Uso de react-hook-form para formulários;
🧭 Rodando a aplicação (Modo desenvolvimento)
git clone https://github.com/LivioAlvarenga/pomodoro-timer # Clone este repositório
cd pomodoro-timer # Acesse a pasta do projeto no seu terminal/cmd
npm install # Instale as dependências
npm run dev # Execute a aplicação em modo de desenvolvimento, a aplicação será aberta na porta:3000 - acesse http://localhost:3000
# Read the observations in home page
Create .npmrc
file with save-exact=true
to save exact version of dependencies. Create only if you want to save exact version of dependencies and do this after install all dependencies
Open .gitignore
and add files folder, because this folder is to put files of project that is not committed in git
🧭 Rodando a aplicação (Modo produção)
npm run build # Compilar o TypeScript em modo de produção
npm run start # Iniciar o servidor em modo de produção
🧭 Testes
...
🚀 Deploy
O deploy foi realizado na plataforma Vercel.com.
As variáveis de ambiente configuradas incluem:
- ...
✅ Good Habits
❗ 1 - Formulários controlled Vz uncontrolled
- Formulários Controlados
Os formulários controlados são adequados quando você precisa de controle granular sobre o estado de cada campo de entrada. No entanto, em formulários com muitos campos, isso pode tornar-se problemático, pois o React precisa atualizar e renderizar novamente o componente a cada mudança em qualquer campo, o que pode prejudicar a performance.
import React, { useState } from "react";
export function formControlled() {
const [nome, setNome] = useState("");
const handleInputChange = (event) => {
setNome(event.target.value);
};
return (
<form>
<label>
Nome:
<input type="text" value={nome} onChange={handleInputChange} />
</label>
<input type="submit" value="Enviar" />
</form>
);
}
- Formulários Não Controlados
Os formulários não controlados são úteis quando você não precisa rastrear o estado de cada campo de entrada em tempo real. Eles são particularmente úteis para formulários simples com poucos campos. No entanto, eles podem ser menos adequados para formulários complexos ou dinâmicos, pois você tem menos controle sobre o estado individual dos campos de entrada.
import React, { useRef } from "react";
export function FormUncontrolled() {
const inputRef = useRef();
const handleSubmit = (event) => {
alert("Um nome foi enviado: " + inputRef.current.value);
event.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
<label>
Nome:
<input type="text" ref={inputRef} />
</label>
<input type="submit" value="Enviar" />
</form>
);
}
❗ 2 - O hook useEffect
O useEffect
é um Hook
em React que permite você executar efeitos colaterais em componentes funcionais. Efeitos colaterais podem ser qualquer coisa que interage com o mundo fora da função do componente, como por exemplo, realizar requisições HTTP, interagir com a API do navegador, realizar assinaturas e timers, e até manipular o DOM diretamente.
Por padrão, o useEffect
é executado após cada renderização. Isso significa que ele é executado após a primeira renderização do componente. E depois, toda vez que o componente for atualizado, o useEffect
será executado novamente.
No entanto, se você passar um array vazio []
como o segundo argumento para useEffect
, isso diz ao React que seu efeito não depende de nenhum valor das props ou do estado, e então ele irá evitar rodar o efeito após cada atualização. Ele apenas rodará uma vez após a primeira renderização
import React, { useState, useEffect } from "react";
import axios from "axios";
export function Teste() {
const [data, setData] = useState(null);
useEffect(() => {
axios
.get("https://api.github.com/users/octocat")
.then((response) => {
setData(response.data);
})
.catch((error) => {
console.error("Algo deu errado!", error);
});
}, []); // A dependência vazia [] significa que este useEffect será executado apenas uma vez
if (data) {
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
);
} else {
return <div>Carregando...</div>;
}
}
O useEffect
também pode ser usado para monitorar um estado específico, de modo que sempre que esse estado for modificado, o efeito seja executado novamente.
Neste exemplo, passamos [count] como segundo argumento para useEffect
. Isso diz ao React para "observar"
o estado count, e sempre que count for modificado, o efeito será executado novamente. Portanto, o título da página sempre será atualizado para refletir a nova contagem sempre que o botão for clicado.
Além disso, temos uma função de limpeza que será executada quando o componente for desmontado, ou antes do próximo efeito ser executado. Esta função restabelece o título da página para 'React App'.
import React, { useState, useEffect } from "react";
export function Teste() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Você clicou ${count} vezes`;
// Função de limpeza
return () => {
document.title = "React App";
};
}, [count]); // monitorando o estado 'count'
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={() => setCount(count + 1)}>Clique aqui</button>
</div>
);
}
Quando NÃO usar o useEffect.
- Cálculos Síncronos:
Se você precisa fazer um cálculo síncrono baseado em props ou estado e usar o resultado na renderização, então useState combinado com a lógica normal de JavaScript dentro do corpo da função do componente é o caminho a percorrer. O useEffect é projetado para lidar com efeitos colaterais e é executado após a renderização, então não seria adequado para cálculos que precisam alterar o que é renderizado.
Exemplo de Mau Uso:
import React, { useState, useEffect } from "react";
export function Teste() {
const [count, setCount] = useState(0);
const [doubleCount, setDoubleCount] = useState(0);
useEffect(() => {
setDoubleCount(count * 2); // mau uso: cálculo síncrono dentro de useEffect
}, [count]);
return (
<div>
<p>
O dobro de {count} é {doubleCount}
</p>
<button onClick={() => setCount(count + 1)}>Incrementar Contagem</button>
</div>
);
}
No exemplo acima, estamos usando useEffect para calcular o dobro do valor de count sempre que count muda. Embora isso funcione, é um mau uso do useEffect porque o cálculo poderia ser feito diretamente no corpo da função do componente.
Exemplo de Bom Uso:
import React, { useState } from "react";
export function Teste() {
const [count, setCount] = useState(0);
const doubleCount = count * 2; // bom uso: cálculo síncrono fora do useEffect
return (
<div>
<p>
O dobro de {count} é {doubleCount}
</p>
<button onClick={() => setCount(count + 1)}>Incrementar Contagem</button>
</div>
);
}
- Operações de Bloqueio:
O useEffect
é executado após a renderização, então qualquer operação que bloqueia a execução do JavaScript fará com que a interface do usuário pareça lenta ou não responsiva. Isso inclui coisas como operações de sincronização pesadas ou uso de métodos como alert ou confirm que bloqueiam a execução até que o usuário responda.
Exemplo de Mau Uso:
import React, { useState, useEffect } from "react";
export function Teste() {
const [count, setCount] = useState(0);
useEffect(() => {
alert(`Você clicou ${count} vezes`); // mau uso: operação de bloqueio dentro do useEffect
}, [count]);
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={() => setCount(count + 1)}>Clique aqui</button>
</div>
);
}
No exemplo acima, estamos usando alert
dentro do useEffect
, o que é uma má prática. O alert
é uma operação de bloqueio porque suspende a execução do JavaScript até que o usuário feche a caixa de diálogo.
Exemplo de Bom Uso:
import React, { useState } from "react";
export function Teste() {
const [count, setCount] = useState(0);
const handleClick = () => {
const newCount = count + 1;
setCount(newCount);
alert(`Você clicou ${newCount} vezes`); // bom uso: operação de bloqueio dentro do manipulador de evento
};
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={handleClick}>Clique aqui</button>
</div>
);
}
O alert
é disparado quando o usuário clica no botão, não após a renderização do componente. Isso torna o código mais eficiente e menos propenso a problemas de desempenho.
No entanto, em geral, é melhor evitar operações de bloqueio como alert
em aplicativos da web modernos, pois elas podem levar a uma experiência de usuário ruim. Considere usar uma solução alternativa, como uma modal ou uma notificação que não bloqueie a execução do JavaScript.
- Efeitos Colaterais Síncronos que Causam Atualizações de Estado:
Se o efeito colateral pode causar uma atualização de estado e é executado síncronamente, ele pode causar uma renderização adicional que pode levar a um ciclo infinito. Por exemplo, atualizando o estado diretamente dentro de um useEffect sem uma lista de dependências ou uma condição de saída.
Exemplo de Mau Uso:
import React, { useState, useEffect } from "react";
export function Teste() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // mau uso: atualizando o estado síncronamente dentro do useEffect
}); // sem lista de dependências, isso irá criar um loop infinito
return (
<div>
<p>{count}</p>
</div>
);
}
No exemplo acima, a função setCount é chamada dentro do useEffect sem uma lista de dependências. Isso causará um loop infinito porque a atualização do estado irá disparar uma nova renderização, que por sua vez disparará o efeito novamente, e assim por diante.
Exemplo de Bom Uso:
import React, { useState, useEffect } from "react";
export function Teste() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count < 10) {
setCount(count + 1); // bom uso: atualizando o estado síncronamente dentro do useEffect com uma condição de saída
}
}, [count]); // adicionando count à lista de dependências para evitar o loop infinito
return (
<div>
<p>{count}</p>
</div>
);
}
- Dependências Omitidas Intencionalmente:
Às vezes, você pode querer omitir intencionalmente uma dependência de um efeito. No entanto, se você fizer isso, pode encontrar bugs que são difíceis de depurar. Se uma variável é usada dentro de um efeito, ela deve estar listada nas dependências.
Exemplo de Mau Uso:
import React, { useState, useEffect } from "react";
export function Teste() {
const [count, setCount] = useState(0);
const [factor, setFactor] = useState(2);
useEffect(() => {
setCount(count * factor); // mau uso: omitindo 'factor' das dependências
}, [count]); // 'factor' deveria estar listado aqui
return (
<div>
<p>{count}</p>
</div>
);
}
No exemplo acima, estamos utilizando factor dentro do useEffect, mas não o incluímos na lista de dependências. Isso pode levar a bugs, pois useEffect não será disparado se factor mudar, o que significa que a contagem pode não ser atualizada corretamente.
Exemplo de Bom Uso:
import React, { useState, useEffect } from "react";
export function Teste() {
const [count, setCount] = useState(0);
const [factor, setFactor] = useState(2);
useEffect(() => {
setCount(count * factor); // bom uso: todas as dependências estão listadas
}, [count, factor]); // 'factor' está listado aqui
return (
<div>
<p>{count}</p>
</div>
);
}
- Substituição de Todos os Métodos de Ciclo de Vida do Componente de Classe:
useEffect
é poderoso, mas não é uma substituição direta para todos os métodos de ciclo de vida do componente de classe (como shouldComponentUpdate
, por exemplo). Tente usar a combinação de diferentes Hooks para obter o comportamento desejado.
Exemplo de Mau Uso:
import React, { useState, useEffect } from "react";
export function Teste() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count < 5) {
return; // tentativa de imitar shouldComponentUpdate com useEffect
}
// restante do código do efeito
}, [count]);
return (
<div>
<p>{count}</p>
</div>
);
}
No exemplo acima, estamos tentando usar useEffect
para imitar o comportamento do shouldComponentUpdate
de um componente de classe. No entanto, isso não funciona como esperado, porque o useEffect
é executado após a renderização, não antes. Portanto, não podemos usar useEffect
para evitar a renderização baseada em uma condição.
Exemplo de Bom Uso:
import React, { useState, useMemo } from "react";
export function Teste() {
const [count, setCount] = useState(0);
const shouldUpdate = useMemo(() => count >= 5, [count]);
if (!shouldUpdate) {
return null; // retorna null para evitar renderização
}
return (
<div>
<p>{count}</p>
</div>
);
}
No exemplo corrigido, usamos o Hook useMemo
para calcular um valor shouldUpdat
e sempre que count muda. Este valor é verdadeiro se count for maior ou igual a 5, e falso caso contrário. Então, nós usamos este valor para decidir se devemos renderizar o componente ou não.
Esta é uma maneira mais eficiente de imitar o comportamento do shouldComponentUpdate
com Hooks. No entanto, lembre-se que este é apenas um exemplo simples. Na prática, pode haver outras maneiras de otimizar a renderização do seu componente, dependendo da sua lógica específica.
❗ 3 - Evitando o Prop drilling
"Prop drilling"
é um termo usado na comunidade React para descrever o processo de obtenção de dados para componentes em níveis mais profundos da árvore de componentes. Isso envolve passar props através de vários componentes que podem não necessariamente precisar desses dados, mas servem como intermediários para passar os dados para componentes filhos mais abaixo na árvore de componentes.
Vamos considerar um exemplo simples para entender melhor.
export function ComponenteA(props) {
return <ComponenteB user={props.user} />;
}
export function ComponenteB(props) {
return <ComponenteC user={props.user} />;
}
export function ComponenteC(props) {
return <p>Olá, {props.user.name}!</p>;
}
No exemplo acima, a ComponenteA
recebe uma prop user
que é passada para ComponenteB
, que por sua vez passa para ComponenteC
. Embora ComponenteB
não use a prop user
, ele precisa aceitá-la e passá-la adiante para ComponenteC
. Esse é o "prop drilling"
.
A desvantagem do prop drilling
é que pode tornar o código difícil de manter e entender, especialmente se a árvore de componentes é muito grande e/ou os props precisam ser passados para componentes muito profundos na árvore.
Para evitar prop drilling
, muitas vezes é utilizada a Context API
do React ou bibliotecas como Redux
ou MobX
, que permitem que você acesse os dados diretamente de qualquer componente, sem precisar passá-los manualmente através de todos os componentes intermediários.
❗ 4 - Context API
O Context API
é uma ferramenta do React que facilita o compartilhamento de estado
e outros dados entre vários componentes sem a necessidade de passá-los explicitamente por meio de props. Isso é especialmente útil quando você precisa compartilhar dados entre componentes que estão em diferentes níveis de aninhamento.
Vamos entender isso com um exemplo. Suponha que temos um tema que queremos compartilhar entre vários componentes em nossa aplicação:
Primeiro, precisamos criar um contexto. No React, criamos um contexto usando React.createContext()
.
import React from "react";
const ThemeContext = React.createContext();
Depois de criar um contexto, nós o fornecemos usando um Provider
. O Provider
aceita um valor de propriedade que contém o valor atual do contexto.
import React from "react";
import { ThemeContext } from "./ThemeContext";
function App() {
return (
<ThemeContext.Provider value="dark">
<ComponentA />
</ThemeContext.Provider>
);
}
Neste exemplo, todos os componentes filhos de <App />
terão acesso ao valor atual do ThemeContext
, que é "dark"
.
Dentro de um componente filho, podemos acessar o valor do contexto usando o Hook React.useContext()
.
import React from "react";
import { ThemeContext } from "./ThemeContext";
function ComponentA() {
const theme = React.useContext(ThemeContext);
return <div>The current theme is: {theme}</div>;
}
Em ComponentA
, estamos usando React.useContext()
para acessar o valor do ThemeContext
. O valor que obtemos é "dark
", que é o valor atual que definimos no ThemeContext.Provider
no componente App.
O Context API
pode ser uma ferramenta muito útil para gerenciar o estado da aplicação
em vários componentes sem a necessidade
de passar explicitamente props
por muitos níveis de componentes aninhados.
❗ 5 - useState Vs useReducer
O useReducer()
é um hook do React que é usado para o gerenciamento de estado. Similar ao useState
, o useReducer
permite que você manipule e controle o estado de seu componente. No entanto, o useReducer
é tipicamente preferido quando você tem lógica de estado complexa
que envolve múltiplos sub-valores
ou quando o próximo estado depende do anterior
. Ele também permite que você otimize a performance para componentes que acionam atualizações profundas porque você pode enviar ações no lugar de callbacks.
Vamos ver a sintaxe do useReducer
:
const [state, dispatch] = useReducer(reducer, initialState);
O useReducer
aceita um reducer
e um initialState
como argumentos e retorna o estado atual e uma função de despacho (dispatch
). A função de despacho pode ser usada para despachar ações que são capturadas pelo reducer.
Aqui está um exemplo simples de como usar useReducer
:
import React, { useReducer } from "react";
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
export function Teste() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Contagem: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
No exemplo acima, temos um estado inicial {count: 0} e um reducer
que manipula duas ações: increment
e decrement
. No componente Teste, usamos useReducer
para obter o estado atual e a função de despacho. Usamos essa função de despacho para despachar ações quando os botões são clicados.
Em comparação, aqui está como você pode fazer algo semelhante com useState
:
import React, { useState } from "react";
export function Teste() {
const [count, setCount] = useState(0);
return (
<>
Contagem: {count}
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</>
);
}
Ambos os exemplos fazem basicamente a mesma coisa
. No entanto, você pode notar que a versão useState
é um pouco mais simples e direta para este caso de uso. Como regra geral, se o estado de seu componente é simples (por exemplo, um único valor que pode ser incrementado ou decrementado), useState
é uma boa opção. Se o seu estado é mais complexo ou depende do estado anterior, useReducer
pode ser mais apropriado.
O useReducer
tende a ser mais útil em situações onde a lógica do estado é mais complexa
ou quando há valores múltiplos interdependentes. Ele também pode ser uma boa escolha quando você deseja organizar sua lógica de estado em ações e reduções de estado, tornando-a mais previsível e testável.
Vamos considerar um exemplo de um formulário com vários campos de input. A lógica de estado aqui pode se tornar complexa rapidamente, pois o estado de cada campo de input pode depender do valor de outros campos.
Aqui está um exemplo de como isso pode ser feito com useReducer
:
import React, { useReducer } from "react";
const initialState = {
nome: "",
sobrenome: "",
idade: "",
email: "",
};
function reducer(state, action) {
return {
...state,
[action.field]: action.value,
};
}
export function Formulario() {
const [state, dispatch] = useReducer(reducer, initialState);
const onChange = (e) => {
dispatch({ field: e.target.name, value: e.target.value });
};
return (
<form>
<input
name="nome"
value={state.nome}
onChange={onChange}
placeholder="Nome"
/>
<input
name="sobrenome"
value={state.sobrenome}
onChange={onChange}
placeholder="Sobrenome"
/>
<input
name="idade"
value={state.idade}
onChange={onChange}
placeholder="Idade"
/>
<input
name="email"
value={state.email}
onChange={onChange}
placeholder="Email"
/>
</form>
);
}
No exemplo acima, usamos useReducer
para gerenciar o estado de vários campos de input. Nós despachamos uma ação sempre que um campo de input muda, atualizando o estado desse campo específico.
Fazer algo semelhante com useState
seria mais complicado. Você precisaria chamar useState
para cada campo de input e gerenciar o estado de cada um individualmente, o que rapidamente se torna confuso e difícil de gerenciar.
O useReducer
também pode ser útil quando você precisa saber sobre o estado anterior para calcular o novo estado. Um exemplo disso seria quando você está implementando algo como um contador que pode ser incrementado, decrementado, dobrado, ou resetado para um valor inicial.
Em suma, useState
é frequentemente mais apropriado para estados simples, enquanto useReducer
é uma boa escolha para lógica de estado mais complexa.
🦸 Autor
Olá, eu sou Livio Alvarenga, Engenheiro de Produção | Dev Back-end e Front-end. Sou aficcionado por tecnologia, programação, processos e planejamento. Uni todas essas paixões em uma só profissão. Dúvidas, sugestões e críticas são super bem vindas. Seguem meus contatos.
📝 Licença
Este projeto é MIT licensed.