Eventos, efectos y estados en React
- useState
- useEffect
- eventos
Inicialización del proyecto
Pasos para configurar un nuevo proyecto, probablemente utilizando herramientas comoVite, o similar.
# Usando Vite
npm create vite@latest my-project --template react
cd my-project
npm install
npm run dev
Sobre los estilos del proyecto
Discute cómo se gestionan los estilos en el proyecto (CSS, módulos CSS, styled-components, etc.).
/* Ejemplo básico de estilos */
.container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.board {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(3, 100px);
gap: 5px;
}
Dibujando el tablero
Cómo renderizar la interfaz visual del tablero del juego (por ejemplo, un tablero de Tic-Tac-Toe). Esto implica renderizar componentes basados en datos.
// Ejemplo básico de cómo dibujar un tablero
function Board({ squares, onClick }) {
return (
<div className="board">
{squares.map((square, i) => (
<button key={i} className="square" onClick={() => onClick(i)}>
{square}
</button>
))}
</div>
);
}
Inicializar el estado
Configurando el estado inicial de la aplicación, probablemente usando el hook useState
en React.
import { useState } from 'react';
function Game() {
const [board, setBoard] = useState(Array(9).fill(null));
// ...otros estados
}
Estado con los turnos
Gestión de qué jugador tiene el turno actual, también usando useState
.
import { useState } from 'react';
function Game() {
const [board, setBoard] = useState(Array(9).fill(null));
const [isXNext, setIsXNext] = useState(true); // true si es turno de X, false si es turno de O
// ...
}
Actualizando el tablero al hacer click
Implementación de la lógica para manejar un click en una celda del tablero y actualizar el estado correspondiente.
function Game() {
const [board, setBoard] = useState(Array(9).fill(null));
const [isXNext, setIsXNext] = useState(true);
const handleClick = (index) => {
// Crear una copia del tablero para no mutar el estado directamente
const newBoard = [...board];
// Lógica para actualizar la celda si está vacía
if (newBoard[index] === null) {
newBoard[index] = isXNext ? 'X' : 'O';
setBoard(newBoard);
setIsXNext(!isXNext); // Cambiar el turno
}
};
return (
<Board squares={board} onClick={handleClick} />
);
}
Lógica del tablero
Discusión sobre las reglas del juego y cómo se aplican (por ejemplo, no permitir clicks en celdas ocupadas).
Ajustando el final del juego
Cómo determinar cuándo el juego ha terminado (victoria, empate).
Detectar el ganador
Implementación de la función o lógica para verificar si hay un ganador en el estado actual del tablero.
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a]; // Retorna 'X' o 'O' si hay ganador
}
}
return null; // No hay ganador
}
El estado es asíncrono
Explicación importante sobre cómo las actualizaciones de estado en React (usando setState
) pueden ser asíncronas y cómo manejarlo.
// Esto puede no reflejar el estado más reciente inmediatamente
setCount(count + 1);
console.log(count); // Puede mostrar el valor anterior
Cómo crear el reset del juego
Implementar una función o botón para reiniciar el juego a su estado inicial.
function Game() {
const [board, setBoard] = useState(Array(9).fill(null));
const [isXNext, setIsXNext] = useState(true);
const resetGame = () => {
setBoard(Array(9).fill(null));
setIsXNext(true);
};
// ...render
return (
<div>
<Board squares={board} onClick={handleClick} />
<button onClick={resetGame}>Reiniciar Juego</button>
</div>
);
}
Lanzar confetti cuando gane la partida
Añadir efectos visuales, como confetti, al detectar que un jugador ha ganado. Esto a menudo implica renderizado condicional o uso de bibliotecas de animación.
// Ejemplo conceptual usando renderizado condicional
function Game() {
// ...estados y lógica
const winner = calculateWinner(board);
return (
<div>
{winner && <ConfettiAnimation />} {/* Mostrar confetti si hay ganador */}
<Board squares={board} onClick={handleClick} />
{/* ... */}
</div>
);
}
Buenas prácticas separación de constante
Discutir la importancia de definir constantes importantes (como las combinaciones ganadoras) separadamente para mejorar la legibilidad y mantenibilidad.
// En un archivo de constantes (constants.js)
export const WINNING_COMBINATIONS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
// ...otras combinaciones
];
// En el componente
import { WINNING_COMBINATIONS } from './constants';
function calculateWinner(squares) {
for (let i = 0; i < WINNING_COMBINATIONS.length; i++) {
const [a, b, c] = WINNING_COMBINATIONS[i];
// ...
}
// ...
}
Persistir la partida en localStorage
Cómo guardar el estado del juego en localStorage
del navegador para que persista entre recargas de página.
// Guardar estado
localStorage.setItem('gameBoard', JSON.stringify(board));
localStorage.setItem('isXNext', JSON.stringify(isXNext));
// Recuperar estado al inicio
const savedBoard = JSON.parse(localStorage.getItem('gameBoard'));
const savedIsXNext = JSON.parse(localStorage.getItem('isXNext'));
const [board, setBoard] = useState(savedBoard || Array(9).fill(null));
const [isXNext, setIsXNext] = useState(savedIsXNext !== null ? savedIsXNext : true);
No puedes usar localStorage en el servidor
Una advertencia importante sobre el uso de APIs del navegador como localStorage
en entornos de renderizado del lado del servidor (SSR), ya que no están disponibles.
// Código que falla en SSR
if (typeof window !== 'undefined') {
// Este código solo se ejecuta en el navegador
localStorage.setItem('test', 'value');
}
Aprende el hook useEffect
Introducción al hook useEffect
para realizar efectos secundarios en componentes funcionales (suscripciones, llamadas a API, manipulación del DOM, etc.).
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Este código se ejecuta después del renderizado inicial
// y después de cada actualización (por defecto)
console.log('Componente renderizado o actualizado');
// Opcional: una función de limpieza
return () => {
console.log('Limpieza');
};
}); // Sin array de dependencias
// ...render
}
Configurar linter para monorepo
Pasos para configurar herramientas de linting y formateo (ESLint, Prettier) en una estructura de monorepo.
// Ejemplo de archivo .eslintrc.js en la raíz del monorepo
module.exports = {
root: true,
extends: ['eslint:recommended'],
settings: {
react: {
version: 'detect',
},
},
overrides: [
{
files: ['*.js', '*.jsx'],
extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2020,
sourceType: 'module',
},
},
// ...otras configuraciones para diferentes paquetes/lenguajes
],
};
Creamos el segundo proyecto y aprendemos useEffect
Aplicar los conocimientos de useEffect
en un nuevo contexto o proyecto para reforzar el aprendizaje.
import { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []); // Array de dependencias vacío: se ejecuta solo una vez al montar
if (loading) return <p>Cargando...</p>;
return <p>Datos: {JSON.stringify(data)}</p>;
}
Limpiando efectos
Explicar la importancia de la función de retorno en useEffect
para limpiar recursos (suscripciones, timers, etc.) cuando el componente se desmonta o el efecto se re-ejecuta.
import { useEffect } from 'react';
function Timer() {
useEffect(() => {
const intervalId = setInterval(() => {
console.log('Tick');
}, 1000);
// Función de limpieza
return () => {
console.log('Limpiando intervalo');
clearInterval(intervalId);
};
}, []); // Se ejecuta solo una vez
}
¿Por qué se ejecuta dos veces el efecto?
Discusión sobre el comportamiento del useEffect
ejecutándose dos veces en desarrollo cuando el Strict Mode de React está activado.
// En index.js o main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode> {/* <-- Strict Mode aquí */}
<App />
</React.StrictMode>,
);
// Cuando App tiene un useEffect simple, se ejecutará dos veces
// en desarrollo debido a Strict Mode. Esto ayuda a detectar efectos con limpieza incorrecta.