React
Tutorial React: Hooks para gestión de estado complejo y contexto
React Hooks: Aprende gestión de estado complejo y contexto con useContext, useReducer y useRef en React con ejemplos prácticos y buenas prácticas.
Aprende React GRATIS y certifícateuseContext
para compartir estado global
El hook useContext
en React permite compartir de manera eficiente el estado global entre componentes sin necesidad de pasar props manualmente en cada nivel del árbol de componentes. Esto es especialmente útil en aplicaciones de gran escala donde el estado o las funciones necesitan ser accesibles en múltiples componentes en diferentes niveles de la jerarquía.
Para utilizar useContext
, primero debes crear un contexto con React.createContext
. Este contexto actúa como un contenedor para el estado global que deseas compartir.
// context/GlobalContext.js
import { createContext } from 'react';
const GlobalContext = createContext();
export default GlobalContext;
Una vez creado el contexto, necesitas un proveedor (Provider
) para envolver los componentes que necesitan acceso al estado global. El proveedor recibe una prop value
que contiene el estado o funciones que deseas compartir.
// context/GlobalProvider.jsx
import { useState } from 'react';
import GlobalContext from '../../context/GlobalContext';
const initialState = {
someValue: false,
};
export default function GlobalProvider({ children }) {
const [state, setState] = useState(initialState);
const value = {
state,
setState,
};
return (
<GlobalContext.Provider value={value}>
{children}
</GlobalContext.Provider>
);
}
Envuelve tu aplicación o la parte relevante de tu aplicación con el GlobalProvider
para que los componentes hijos puedan acceder al contexto.
// App.jsx
import YourComponent from './components/YourComponent/YourComponent';
import GlobalProvider from './context/GlobalProvider';
export default function App() {
return (
<GlobalProvider value={true}>
<YourComponent />
</GlobalProvider>
);
}
Dentro de los componentes, puedes utilizar el hook useContext
para acceder al contexto y, por lo tanto, al estado global.
// components/YourComponent/YourComponent.jsx
import { useContext } from 'react';
import GlobalContext from '../../context/GlobalContext';
export default function YourComponent() {
const { state, setState } = useContext(GlobalContext);
const handleClick = () => {
setState(prevState => ({
...prevState,
someValue: !prevState.someValue,
}));
};
return (
<div>
<p>{state.someValue.toString()}</p>
<button onClick={handleClick}>Toggle Value</button>
</div>
);
}
En este ejemplo, YourComponent
accede al estado global y a la función setState
a través de useContext
. El botón dentro del componente cambia el valor de someValue
en el estado global, demostrando cómo useContext
facilita la modificación del estado compartido.
Es importante tener en cuenta que el uso de useContext
es adecuado para compartir estado que no cambia frecuentemente. Para estados que cambian con alta frecuencia, es preferible utilizar contextos más específicos o combinar useContext
con otros hooks como useReducer
para un manejo más eficiente del estado.
useReducer
para manejo de estado completo
El hook useReducer
en React es una alternativa a useState
para manejar estados complejos que involucran múltiples sub-valores o cuando la lógica de actualización del estado es más avanzada. Es similar a los reducers en Redux, pero integrado en el ecosistema de React.
Para utilizar useReducer
, necesitas definir un reducer, que es una función que toma el estado actual y una acción, y devuelve un nuevo estado. La estructura básica de un reducer es la siguiente:
// Counter/reducer.js
export default 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(`Unhandled action type: ${action.type}`);
}
}
A continuación, puedes utilizar useReducer
en tu componente para gestionar el estado. useReducer
recibe el reducer y un estado inicial, y devuelve el estado actual y una función dispatch
para enviar acciones.
// Counter/Counter.jsx
import { useReducer } from 'react';
import reducer from './reducer';
const initialState = { count: 0 };
export default function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
En este ejemplo, Counter
utiliza useReducer
para gestionar el estado del contador. Al hacer clic en los botones, se envían acciones al reducer, que actualiza el estado en consecuencia.
useReducer
es especialmente útil cuando:
- El estado tiene una estructura compleja.
- La lógica de actualización del estado es compleja.
- Necesitas optimizar el rendimiento para evitar recrear funciones en cada renderizado.
También puedes combinar useReducer
con useContext
para gestionar el estado global de manera más eficiente. Primero, define el contexto y el proveedor como en la sección de useContext
.
// context/GlobalContext.js
import { createContext } from 'react';
const GlobalContext = createContext();
export default GlobalContext;
Luego, crea un proveedor que use useReducer
en lugar de useState
.
// providers/GlobalProvider.jsx
import { useReducer } from 'react';
import GlobalContext from '../../context/GlobalContext';
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(`Unhandled action type: ${action.type}`);
}
}
export default function GlobalProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch };
return (
<GlobalContext.Provider value={value}>
{children}
</GlobalContext.Provider>
);
}
Envuelve tu aplicación con este proveedor.
// App.jsx
import GlobalProvider from './context/GlobalProvider';
export default function App() {
return (
<GlobalProvider>
<YourComponent />
</GlobalProvider>
);
}
Dentro de los componentes, puedes usar useContext
para acceder al contexto y despachar acciones.
// components/YourComponent/YourComponent.jsx
import { useContext } from 'react';
import GlobalContext from '../../context/GlobalContext';
export default function YourComponent() {
const { state, dispatch } = useContext(GlobalContext);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
Este enfoque permite gestionar el estado global de manera eficiente utilizando useReducer
, lo que es beneficioso para aplicaciones grandes con estados complejos.
useRef
para acceso a referencias y persistencia de valores
El hook useRef
en React se utiliza para acceder a referencias directas a elementos del DOM y para persistir valores entre renderizados sin causar re-renderizados. Este hook es especialmente útil cuando necesitas manipular directamente el DOM o almacenar valores mutables que no deberían desencadenar un ciclo de renderizado.
Acceso a referencias del DOM
useRef
puede crear una referencia mutable que puedes asignar a un elemento del DOM. Esta referencia no cambia entre renderizados y no provoca un re-renderizado cuando se actualiza.
Ejemplo de cómo utilizar useRef
para acceder a un elemento del DOM:
// FocusInput.jsx
import { useRef } from 'react';
export default function FocusInput() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Enfocar Input</button>
</div>
);
}
En este ejemplo, inputRef
es una referencia que se asigna al elemento <input>
. Al hacer clic en el botón, se llama a inputRef.current.focus()
para enfocar el campo de entrada.
Persistencia de valores entre renderizados
Además de manejar referencias del DOM, useRef
también puede almacenar valores mutables que persisten entre renderizados sin causar un re-renderizado. Esto es útil para mantener valores como contadores, temporizadores, o cualquier valor que necesite persistir sin estar en el estado.
// Timer.jsx
import { useRef, useEffect, useState } from 'react';
export default function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => clearInterval(intervalRef.current)}>Stop Timer</button>
</div>
);
}
En este ejemplo, intervalRef
se utiliza para almacenar la referencia al intervalo. Al desmontar el componente, el intervalo se limpia utilizando clearInterval(intervalRef.current)
. La referencia intervalRef
permite persistir el identificador del intervalo entre renderizados sin causar un re-renderizado adicional.
Casos de uso comunes
- Manipulación del DOM: Acceder y manipular elementos del DOM directamente, como enfocar, seleccionar texto, o cambiar estilos.
- Persistencia de valores: Almacenar valores que no deberían causar un re-renderizado, como identificadores de intervalos, contadores, o cualquier valor mutable que no afecta la UI directamente.
- Interacción con APIs externas: Mantener referencias a objetos que interactúan con APIs externas, como WebSocket, eventos personalizados, o cualquier objeto que necesite persistir entre renderizados.
Consideraciones
useRef
no notifica cuando su contenido cambia. Si necesitas notificaciones o desencadenar re-renderizados, considera utilizaruseState
ouseReducer
.- Es importante diferenciar cuándo utilizar
useRef
en lugar deuseState
.useRef
es ideal para valores que no afectan la renderización de la UI, mientras queuseState
debería usarse para valores que sí afectan la UI.
En resumen, useRef
es una herramienta versátil en React para manejar referencias del DOM y persistir valores entre renderizados, ofreciendo una forma eficiente de interactuar con elementos del DOM y almacenar valores sin causar re-renderizados innecesarios.
Buenas prácticas de useContext
, useReducer
, useRef
Para maximizar la eficiencia y mantenibilidad de aplicaciones React que hacen uso de useContext
, useReducer
y useRef
, es crucial seguir una serie de buenas prácticas. Estas prácticas no solo mejoran el rendimiento, sino que también facilitan la comprensión y el mantenimiento del código.
useContext
Contextos específicos y bien definidos: Evita crear contextos demasiado generales que manejen múltiples estados dispares. En su lugar, crea contextos específicos para diferentes partes de tu aplicación. Por ejemplo, un contexto para la autenticación y otro para la configuración de la aplicación.
Memoización del valor del contexto: Utiliza useMemo
para memoizar el valor pasado al proveedor del contexto. Esto evita renders innecesarios de los componentes consumidores cuando el valor del contexto no ha cambiado.
// context/AuthContext.js
import { createContext } from "react";
const AuthContext = createContext();
export default AuthContext
// providers/AuthProvider.jsx
import { useMemo, useState } from 'react';
import AuthContext from '../../context/AuthContext';
export default function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
- Evitar anidar múltiples contextos: Si necesitas varios contextos, considera usar un patrón de composición para evitar la anidación profunda de proveedores, lo cual puede dificultar la lectura y el mantenimiento del código.
// providers/AppProviders.jsx
const AppProviders = ({ children }) => (
<AuthProvider>
<GlobalProvider>
{children}
</GlobalProvider>
</AuthProvider>
);
export default AppProviders
// components/YourComponent/YourComponent.jsx
import { useContext } from 'react';
import GlobalContext from '../../context/GlobalContext';
import AuthContext from '../../context/AuthContext';
export default function YourComponent() {
const { state, dispatch } = useContext(GlobalContext);
const { user, setUser } = useContext(AuthContext);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
<p>User: {user}</p>
<button onClick={() => setUser('John')}>Set User</button>
</div>
);
}
// App.jsx
import AppProviders from './providers/AppProviders';
import YourComponent from './components/YourComponent/YourComponent';
export default function App() {
return (
<AppProviders>
<YourComponent />
</AppProviders>
);
}
useReducer
- Descomponer reducers complejos: Si tu reducer maneja múltiples tipos de acciones, considera descomponerlo en varios reducers más pequeños y combinarlos utilizando el patrón de "reducers combinados".
// utils/combineReducers.js
export default function combineReducers(reducers) {
return function combination(state = {}, action) {
const nextState = {};
for (const key in reducers) {
nextState[key] = reducers[key](state[key], action);
}
return nextState;
};
}
// reducers.js
import { SET_THEME, SET_USER } from "../../consts/actions.const";
export function userReducer(state, action) {
switch (action.type) {
case SET_USER:
return { name: action.payload };
default:
return state;
}
}
export function settingsReducer(state, action) {
switch (action.type) {
case SET_THEME:
return { ...state, theme: action.payload };
default:
return state;
}
}
// CombinedReducers.jsx
import { useReducer } from 'react';
import { settingsReducer, userReducer } from './reducers';
import combineReducers from '../../utils/combineReducers';
import { SET_THEME, SET_USER } from '../../consts/actions.const';
const combinedReducer = combineReducers({
user: userReducer,
settings: settingsReducer,
});
const initialState = {
user: {
name: null
},
settings: {
theme: 'light'
}
}
export default function CombinedReducers() {
const [{ user, settings }, dispatch] = useReducer(combinedReducer, initialState)
return (
<>
<div>
<p>User: {user.name}</p>
<p>Theme: {settings.theme}</p>
</div>
<button onClick={() => dispatch({ type: SET_USER, payload: 'John Doe' })}>Set User</button>
<button onClick={() => dispatch({ type: SET_THEME, payload: 'dark' })}>Set Theme</button>
</>
)
}
- Acciones bien definidas: Define tus acciones de manera clara y específica. Utiliza constantes para los tipos de acción para evitar errores tipográficos y facilitar el mantenimiento.
// const/actions.const.js
export const SET_USER = Symbol('set_user');
export const SET_THEME = Symbol('set_theme');
- Estado inicial bien estructurado: Define un estado inicial claro y estructurado. Esto facilita la comprensión de la forma del estado y asegura que todos los valores iniciales estén correctamente establecidos.
const initialState = {
user: {
name: null
},
settings: {
theme: 'light'
}
}
useRef
- Uso adecuado para referencias del DOM: Utiliza
useRef
para acceder a elementos del DOM cuando sea necesario, pero evita abusar de este patrón. Solo usauseRef
cuando la manipulación directa del DOM sea imprescindible.
// components/Modal.jsx
import { useRef } from 'react';
export default function Modal({ children }) {
const modalRef = useRef(null);
const openModal = () => modalRef.current.showModal();
const closeModal = () => modalRef.current.close();
return (
<>
<button onClick={openModal}>Open</button>
<dialog ref={modalRef}>
{children}
<button onClick={closeModal}>Close</button>
</dialog>
</>
);
}
- Persistencia de valores no renderizados: Usa
useRef
para almacenar valores que deben persistir entre renderizados sin causar un re-renderizado, como temporizadores o contadores de intentos.
// components/RetryButton.jsx
import { useRef, useState } from 'react';
export default function RetryButton() {
const retryCount = useRef(0);
const [message, setMessage] = useState('');
const handleClick = () => {
retryCount.current += 1;
setMessage(`Intento ${retryCount.current}`);
};
return (
<div>
<button onClick={handleClick}>Retry</button>
{message && <p>{message}</p>}
</div>
);
}
- Evitar la manipulación directa del estado: No uses
useRef
para manipular el estado de los componentes directamente. UtilizauseState
ouseReducer
para cualquier estado que afecte la renderización de la UI para mantener la claridad y previsibilidad del comportamiento del componente.
Ejercicios de esta lección Hooks para gestión de estado complejo y contexto
Evalúa tus conocimientos de esta lección Hooks para gestión de estado complejo y contexto con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Props y manejo de datos entre componentes
Definición y manejo de rutas
Conocimiento general de React
Galería de imágenes en React
Eventos en React
Gestor de tareas con React
Custom Hooks para servicios compartidos
Nuevos métodos create de React Router
Solicitudes HTTP con Fetch API
Instalar React y crear nuevo proyecto
Renderizado condicional
Introducción a JSX
Manejo de clases y estilos
Introducción a React Router
Solicitudes HTTP con Axios
Estado local con useState y useReducer
Estado global con Redux Toolkit
Estado y ciclo de vida de los componentes
Hooks para gestión de estado complejo y contexto
Componentes funcionales
Estado global con Context API
Hooks: optimización y concurrencia
Introducción a React y su ecosistema
Introducción a Componentes
Introducción a los Hooks
Navegación programática y redirección
Renderizado iterativo con bucles
Rutas anidadas y rutas dinámicas
Hooks: estado y efectos secundarios
Todas las lecciones de React
Accede a todas las lecciones de React y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A React Y Su Ecosistema
Introducción Y Entorno
Instalar React Y Crear Nuevo Proyecto
Introducción Y Entorno
Introducción A Jsx
Componentes
Introducción A Componentes
Componentes
Componentes Funcionales
Componentes
Eventos En React
Componentes
Props Y Manejo De Datos Entre Componentes
Componentes
Renderizado Condicional
Componentes
Renderizado Iterativo Con Bucles
Componentes
Manejo De Clases Y Estilos
Componentes
Introducción A Los Hooks
Hooks
Estado Y Ciclo De Vida De Los Componentes
Hooks
Hooks Estado Y Efectos Secundarios
Hooks
Hooks Para Gestión De Estado Complejo Y Contexto
Hooks
Hooks Optimización Y Concurrencia
Hooks
Introducción A React Router
Navegación Y Enrutamiento
Definición Y Manejo De Rutas
Navegación Y Enrutamiento
Rutas Anidadas Y Rutas Dinámicas
Navegación Y Enrutamiento
Navegación Programática Redirección
Navegación Y Enrutamiento
Nuevos Métodos Create De React Router
Navegación Y Enrutamiento
Solicitudes Http Con Fetch Api
Interacción Http Con Backend
Solicitudes Http Con Axios
Interacción Http Con Backend
Estado Local Con Usestate Y Usereducer
Servicios Y Gestión De Estado
Estado Global Con Context Api
Servicios Y Gestión De Estado
Estado Global Con Redux Toolkit
Servicios Y Gestión De Estado
Custom Hooks Para Servicios Compartidos
Servicios Y Gestión De Estado
Evaluación Test React
Evaluación
Certificados de superación de React
Supera todos los ejercicios de programación del curso de React y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender cómo compartir estado global entre componentes con
useContext
. - Implementar lógica de estado complejo utilizando
useReducer
. - Manipular elementos del DOM y persistir valores entre renderizados con
useRef
. - Aplicar buenas prácticas para el uso eficiente y mantenible de
useContext
,useReducer
, yuseRef
.