Curso react nivel medio
Introducción
El hook useSyncExternalStore fue introducido en React 18 como una solución estandarizada para suscribirse a almacenes de estado externos (external stores) en aplicaciones React. Es útil para trabajar con bibliotecas como Redux, Zustand o cualquier sistema de estado que gestione el estado fuera del ciclo de vida de React.
Este hook asegura que React mantenga sincronizado su estado con el estado externo en aplicaciones tanto en Cliente como en Servidor.
¿Qué es useSyncExternalStore?
useSyncExternalStore te permite:
- Suscribirte a un estado externo para recibir actualizaciones.
- Garantizar que el estado se sincroniza correctamente entre renderizados.
- Proveer un estado inicial para renderizado en servidor (SSR).
Sintaxis
1const state = useSyncExternalStore(suscribe, getSnapshot, getServerSnapshot);
suscribe: Una función que se llama para suscribirse a cambios en el estado externo. Debe devolver una función para desuscribirse.getSnapshot: Devuelve el valor actual del estado externo.getServerSnapshot(opcional): Devuelve el estado inicial para renderizado en servidor.
Ejemplo Básico: Contador con Estado Externo
Imagina un contador gestionado fuera de React.
Almacén Externo
1// counterStore.js 2let count = 0; 3let listeners = []; 4 5export const increment = () => { 6 count += 1; 7 listeners.forEach((listener) => listener()); 8}; 9 10export const subscribe = (listener) => { 11 listeners.push(listener); 12 return () => { 13 listeners = listeners.filter((l) => l !== listener); 14 }; 15}; 16 17export const getCount = () => count;
Componente React
1import React from "react"; 2import { useSyncExternalStore } from "react"; 3import { subscribe, getCount, increment } from "./counterStore"; 4 5function Contador() { 6 const count = useSyncExternalStore(subscribe, getCount); 7 8 return ( 9 <div> 10 <h1>Contador: {count}</h1> 11 <button onClick={increment}>Incrementar</button> 12 </div> 13 ); 14} 15 16export default Contador;
Explicación
subscribe: React se suscribe al almacén externo (counterStore).getCount: Devuelve el valor actual del contador.- Sincronización: El componente se renderiza automáticamente cuando el contador cambia.
Ejemplo con Renderizado en Servidor (SSR)
Cuando renderizas React en servidor, necesitas pasar un estado inicial para evitar inconsistencias.
Almacén Externo con Estado Inicial
1// themeStore.js 2let theme = "light"; 3let listeners = []; 4 5export const setTheme = (newTheme) => { 6 theme = newTheme; 7 listeners.forEach((listener) => listener()); 8}; 9 10export const subscribe = (listener) => { 11 listeners.push(listener); 12 return () => { 13 listeners = listeners.filter((l) => l !== listener); 14 }; 15}; 16 17export const getTheme = () => theme; 18 19// Para SSR 20export const getServerTheme = () => "light"; // Estado inicial en servidor
Componente React
1import React from "react"; 2import { useSyncExternalStore } from "react"; 3import { subscribe, getTheme, getServerTheme, setTheme } from "./themeStore"; 4 5function Tema() { 6 const theme = useSyncExternalStore(subscribe, getTheme, getServerTheme); 7 8 return ( 9 <div> 10 <h1>Tema Actual: {theme}</h1> 11 <button onClick={() => setTheme("light")}>Claro</button> 12 <button onClick={() => setTheme("dark")}>Oscuro</button> 13 </div> 14 ); 15} 16 17export default Tema;
Explicación
getServerTheme: Proporciona un valor inicial del tema para renderizado en servidor.- Sincronización Universal: React sincroniza correctamente el tema entre cliente y servidor.
Diferencias con useState y useReducer
| Característica | useSyncExternalStore | useState / useReducer |
|---|---|---|
| Fuente del Estado | Externo (almacén global o biblioteca) | Interno al componente |
| Sincronización | Garantiza sincronización con React | No aplica, el estado es propio |
| Renderizado en Servidor | Compatible con SSR | No diseñado específicamente para SSR |
Comparación con useEffect
Aunque podrías suscribirte a un almacén externo con useEffect, useSyncExternalStore es más seguro y eficiente.
Con useEffect
1import React, { useState, useEffect } from "react"; 2import { subscribe, getCount } from "./counterStore"; 3 4function Contador() { 5 const [count, setCount] = useState(getCount()); 6 7 useEffect(() => { 8 const unsubscribe = subscribe(() => { 9 setCount(getCount()); 10 }); 11 return unsubscribe; 12 }, []); 13 14 return ( 15 <div> 16 <h1>Contador: {count}</h1> 17 </div> 18 ); 19} 20 21export default Contador;
Con useSyncExternalStore
1import React from "react"; 2import { useSyncExternalStore } from "react"; 3import { subscribe, getCount } from "./counterStore"; 4 5function Contador() { 6 const count = useSyncExternalStore(subscribe, getCount); 7 8 return ( 9 <div> 10 <h1>Contador: {count}</h1> 11 </div> 12 ); 13} 14 15export default Contador;
Diferencias
| Aspecto | useEffect | useSyncExternalStore |
|---|---|---|
| Código más limpio | No | Sí |
| Renderizado en Servidor (SSR) | No soportado | Soportado |
| Eficiencia | Menos eficiente | Más eficiente |
Ventajas de useSyncExternalStore
-
Integración estándar:
- Trabaja perfectamente con React Concurrent Mode y SSR.
-
Simplifica código:
- Proporciona una solución directa para manejar suscripciones sin usar
useEffect.
- Proporciona una solución directa para manejar suscripciones sin usar
-
Evita Renderizados Innecesarios:
- Solo actualiza el componente cuando el estado externo cambia.
Limitaciones
-
Solo para Estados Externos:
- No reemplaza
useStateouseReducerpara manejar estado interno del componente.
- No reemplaza
-
Mayor Configuración Inicial:
- Requiere definir funciones como
subscribeygetSnapshot.
- Requiere definir funciones como
-
Complejidad para Nuevos Desarrolladores:
- Puede ser más difícil de entender en comparación con
useStateouseEffect.
- Puede ser más difícil de entender en comparación con
Conclusión
useSyncExternalStore es una herramienta poderosa para sincronizar React con sistemas de estado externos, ofreciendo soporte para SSR y Concurrent Mode. Es ideal para casos como:
- Trabajar con bibliotecas de estado externo (Redux, Zustand).
- Sincronizar componentes React con estados globales fuera del ciclo de vida de React.