En React, los hooks son funciones especiales que te permiten usar características como el estado y el ciclo de vida en componentes funcionales. Los hooks fueron introducidos en React 16.8 y son fundamentales para escribir componentes más limpios y reutilizables.
Prerrequisitos
- Instalación react se explica a detalle como instar y configurar un entono de trabajo para react.
- Estructura de un proyecto React Conocer los archivos principales, index.js, App.js, carpetas, etc.
- JSX Sintaxis básica, reglas JSX, expresiones, fragmentos (<> </>).
- En el post de componentes en React se dan los primeros pasos para crear un componente.
Crear un proyecto de prueba
Antes de explicar los Hooks, vamos a crear un proyecto react para realizar los ejercicios que explicaremos a continuación:
npm create vite@latest
Donde seleccionamos:
> npx
> create-vite
|
o Project name:
| practicas-react
|
o Select a framework:
| React
|
o Select a variant:
| TypeScript
Por ultimo, ejecutamos
cd practicas-react
npm install
npm run dev
Vamos a crear un directorio llamado components
en el directorio src
donde crearemos los componentes de los hooks
Hooks Básicos
⏺️ useState
Permite a los componentes funcionales gestionar su estado, lo que antes se hacía solo en las clases. Al usar useState
, puedes añadir una variable de estado que se mantiene entre renderizaciones y actualizarla, haciendo que tu componente sea dinámico y reaccione a las interacciones.
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Contador: {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
);
}
- Se importa el hook
useState
, que permite agregar estado a un componente funcional. - Se declara una función que representa un componente llamado
Counter
. - Uso de
useState
count
→ Es el valor actual del estado (en este caso, el número del contador).setCount
→ Es la función para actualizar ese estado.useState(0)
→ Inicializa el estado con0
.
- Retorno del componente (JSX)
<p>Contador: {count}</p>
: Muestra el valor actual del contador.<button onClick={...}>
: Es un botón que, al hacer clic, incrementa el contador en 1 usandosetCount
.
Siempre que necesites manejar un estado local en un componente funcional.
⏺️ useEffect
Permite realizar efectos secundarios en componentes funcionales, como peticiones de datos, actualizaciones del DOM, o configurar suscripciones.
import { useEffect, useState } from "react";
export default function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("https://timeapi.io/api/Time/current/zone?timeZone=America/Bogota")
.then((response) => response.json())
.then((data) => setData(data));
}, []); // [] significa que solo se ejecuta en el montaje
return <div>{data ? JSON.stringify(data) : "Cargando..."}</div>;
}
- Importación de hooks
- useState: Para manejar el estado del componente.
- useEffect: Para manejar efectos secundarios, como llamadas a APIs.
- Este es un componente funcional llamado DataFetcher, y se está exportando como componente predeterminado (default).
- Estado local useState: El estado comienza en null, lo que indica que aún no se ha recibido la información.
- Efecto con useEffect
- useEffect ejecuta el bloque de código una vez que el componente se monta.
- El array vacío [] indica que se ejecuta una sola vez, como si fuera componentDidMount en componentes de clase.
- Dentro del efecto se hace una llamada a la API https://timeapi.io/... para obtener la hora actual en Bogotá.
- Una vez que la respuesta llega, se convierte a JSON, y se guarda en el estado con setData(data).
- Renderizado condicional
- Si data ya tiene valor, se muestra como texto usando JSON.stringify(data) (convierte el objeto en un string).
- Si data todavía es null, se muestra "Cargando...".
⏺️ useContext
useContext es un Hook de React que te permite acceder y suscribirte a un contexto desde tus componentes funcionales. Este Hook facilita la gestión de datos compartidos entre componentes sin necesidad de pasar props a través de múltiples niveles de jerarquía.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function ThemedComponent() {
const theme = useContext(ThemeContext);
return <div>El tema actual es {theme}</div>;
}
Cuando quieras compartir datos globales entre componentes (como temas o usuarios autenticados).
Hooks Adicionales
⏺️ useReducer
useReducer es un Hook de React que facilita la gestión de estados complejos dentro de componentes funcionales, similar al patrón de reducción en Redux, pero integrado directamente en React. Es una alternativa a useState cuando la lógica para actualizar el estado se vuelve más compleja, permitiendo el uso de una función reductora para manejar las actualizaciones de estado.
import React, { useReducer } from 'react';
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 default function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Contador: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>
Incrementar
</button>
<button onClick={() => dispatch({ type: "decrement" })}>
Decrementar
</button>
</div>
);
}
⏺️ useRef
useRef devuelve un objeto ref mutable cuya propiedad . current se inicializa con el argumento pasado ( initialValue ). El objeto devuelto se mantendrá persistente durante la vida completa del componente. En esencia, useRef es como una “caja” que puedes mantener en una variable mutable en su propiedad .
import React, { useRef } from 'react';
function InputFocus() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focalizar Input</button>
</div>
);
}
Para acceder a elementos del DOM o almacenar valores mutables.
⏺️ useMemo
Memoriza un valor calculado para evitar recomputaciones costosas.
import React, { useMemo } from 'react';
function ExpensiveCalculation({ num }) {
const factorial = useMemo(() => {
console.log('Calculando factorial...');
return num <= 0 ? 1 : num * factorial(num - 1);
}, [num]);
return <div>Factorial: {factorial}</div>;
}
⏺️ useCallback
Memoriza una función para evitar su recreación en cada render.
import React, { useCallback } from 'react';
function Parent({ onClick }) {
const handleClick = useCallback(() => {
console.log('Clickeado');
}, []);
return <button onClick={handleClick}>Click me</button>;
}
Cuando necesites pasar funciones a componentes hijos para evitar renders innecesarios.
Hooks Avanzados
Personaliza la instancia del componente expuesta al componente padre cuando se usa React.forwardRef.
src/
├── components/
│ └── CustomInput.tsx
├── App.tsx
└── main.tsx
Creamos CustomInput.tsx – Componente con useImperativeHandle
// src/components/CustomInput.tsx
import React, { useImperativeHandle, useRef, forwardRef } from 'react';
// Definimos los métodos que el padre podrá usar
export interface CustomInputHandle {
focus: () => void;
clear: () => void;
}
// Usamos forwardRef para pasar el ref desde el padre
const CustomInput = forwardRef<CustomInputHandle>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
},
clear: () => {
if (inputRef.current) {
inputRef.current.value = '';
}
}
}));
return <input ref={inputRef} placeholder="Escribe algo..." />;
});
export default CustomInput;
Modificamos el App.tsx – El componente padre usa el ref
import { useRef } from "react";
import "./App.css";
import CustomInput, { type CustomInputHandle } from "./components/CustomInput";
function App() {
const inputRef = useRef<CustomInputHandle>(null);
const handleFocus = () => {
inputRef.current?.focus();
};
const handleClear = () => {
inputRef.current?.clear();
};
return (
<div style={{ padding: "40px" }}>
<h1>useImperativeHandle Example</h1>
<CustomInput ref={inputRef} />
<div style={{ marginTop: "10px" }}>
<button onClick={handleFocus} style={{ marginRight: "10px" }}>
Focus
</button>
<button onClick={handleClear}>Clear</button>
</div>
</div>
);
}
export default App;
Tendrás un campo de texto con dos botones:
- Al hacer clic en "Focus", el input se enfoca.
- Al hacer clic en "Clear", se borra su contenido.
⏺️ useLayoutEffect
useLayoutEffect es un Hook de React similar a useEffect, pero se ejecuta de forma sincrónica después de que se actualiza el DOM, pero antes de que el navegador repinte la pantalla. Esto lo hace ideal para situaciones donde necesitas medir o manipular el DOM y asegurarte de que los cambios se vean antes de que el navegador renderice el siguiente fotograma.
Ejemplo
Estructura de archivos
src/
├── components/
│ └── BoxWithSize.tsx
├── App.tsx
└── main.tsx
Creamos BoxWithSize.tsx (componente que usa useLayoutEffect)
import React, { useLayoutEffect, useRef, useState } from "react";
const BoxWithSize: React.FC = () => {
const boxRef = useRef<HTMLDivElement>(null);
const [size, setSize] = useState({ width: 0, height: 0 });
useLayoutEffect(() => {
if (boxRef.current) {
const { width, height } = boxRef.current.getBoundingClientRect();
setSize({ width, height });
}
}, []); // Solo se ejecuta una vez después del montaje y antes del repintado
return (
<div>
<div
ref={boxRef}
style={{
width: "200px",
height: "150px",
backgroundColor: "lightblue",
margin: "20px",
}}
>
Soy una caja
</div>
<p>
Mi tamaño es: {size.width}px × {size.height}px
</p>
</div>
);
};
export default BoxWithSize;
✅ Explicación paso a paso
- useRef crea una referencia al <div> con el tamaño que queremos medir.
- useLayoutEffect se ejecuta después del DOM renderizado pero antes de que el navegador pinte la pantalla. Es ideal para leer o modificar el layout.
- Usamos getBoundingClientRect() para obtener el ancho y alto de la caja.
- Guardamos esas dimensiones en el estado para mostrarlas en pantalla.
- Se renderiza el contenido con los valores ya leídos, sin parpadeos visuales.
Hooks Personalizados
Puedes crear tus propios hooks para reutilizar lógica que involucre otros hooks. Por ejemplo:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
});
}, [url]);
return { data, loading };
}
Fases del ciclo de vida de un componente
El ciclo de vida de un componente en React describe las etapas por las que pasa un componente desde que es creado, actualizado y eventualmente eliminado. Aunque en los componentes funcionales se gestiona principalmente con hooks, el concepto sigue siendo el mismo.
A continuación, te explico cómo funciona el ciclo de vida tanto en componentes de clase como en componentes funcionales con hooks:
⏺️ Montaje (Mounting)
Es el proceso de creación e inserción del componente en el DOM.
🟢 Métodos en Componentes de Clase
- constructor(props): Se ejecuta antes de que el componente se monte. Se usa para inicializar el estado o enlazar métodos.
- static getDerivedStateFromProps(props, state): Se llama justo antes del render, útil para sincronizar el estado con las props.
- render(): Genera el árbol de elementos React que se inserta en el DOM.
- componentDidMount(): Se llama después de que el componente se haya montado en el DOM. Aquí puedes realizar llamadas a APIs o configurar eventos.
🟢 Hooks en Componentes Funcionales
useEffect(() => { ... }, []):
El equivalente a componentDidMount. El efecto se ejecuta después del montaje si el segundo argumento es un array vacío.
⏺️ Actualización (Updating)
Se da cuando el componente detecta cambios en sus props o estado.
🟢 Métodos en Componentes de Clase
static getDerivedStateFromProps(props, state)
: Se llama cada vez que cambian las props.shouldComponentUpdate(nextProps, nextState)
: Controla si el componente debe renderizarse nuevamente.render()
: Se llama para generar el nuevo árbol de elementos React.getSnapshotBeforeUpdate(prevProps, prevState)
: Captura información antes de que se actualice el DOM (como el scroll).componentDidUpdate(prevProps, prevState, snapshot)
: Se llama después de que el componente se haya actualizado. Útil para manejar efectos secundarios basados en cambios en props o estado.
🟢 Hooks en Componentes Funcionales
useEffect(() => { ... }
, [dependencias]): Se ejecuta cada vez que cambian las dependencias especificadas.- Sin equivalente directo: No hay hooks específicos como shouldComponentUpdate, pero puedes optimizar renderizados con React.memo.
⏺️ Desmontaje (Unmounting)
Es el proceso de eliminar un componente del DOM.
🟢 Métodos en Componentes de Clase
componentWillUnmount()
: Se llama justo antes de que el componente sea eliminado del DOM. Se usa para limpiar eventos, timers o suscripciones.
🟢 Hooks en Componentes Funcionales
useEffect(() => { return () => { ... } }, [])
: La función de limpieza (cleanup) es el equivalente a componentWillUnmount.