useSyncExternalStore
useSyncExternalStore
es un Hook de React que te permite suscribirte a una fuente de almacenamiento de datos externa.
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Referencia
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Llame a useSyncExternalStore
en el nivel superior de su componente para leer un valor de una fuente de almacenamiento de datos externa.
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
// ...
}
Devuelve la instantánea de los datos en la fuente de almacenamiento de datos externa. Necesitas pasar dos funciones como argumentos:
- La función
subscribe
debe suscribirse a la fuente de almacenamiento de datos externa y devolver una función que cancela dicha suscripción. - La función
getSnapshot
debería obtener una instantánea de los datos de la fuente de almacenamiento de datos externa.
Parametros
-
subscribe
: Una función que toma un solo argumentocallback
y lo suscribe a la fuente de almacenamiento de datos externa. Cuando la fuente de almacenamiento de datos externa cambia, debe invocar elcallback
proporcionado. Esto hará que el componente se vuelva a rerenderizar. La funciónsubscribe
debería devolver una función que limpia dicha suscripción. -
getSnapshot
: Una función que devuelve una instantánea de los datos de la fuente de almacenamiento externa de datos que necesita el componente. Si bien la fuente de almacenamiento externa de datos no ha cambiado, las llamadas repetidas agetSnapshot
deben devolver el mismo valor. Si la fuente de almacenamiento externa de datos cambia y el valor devuelto es diferente (usando para la comparaciónObject.is
), React volverá a rerenderizar el componente. -
opcional
getServerSnapshot
: Una función que devuelve la instantánea inicial de los datos de la fuente de almacenamiento externa de datos. Se usará solo durante la renderización desde el servidor y durante la hidratación del contenido renderizado por el servidor en el cliente. La instantánea del servidor debe ser la misma entre el cliente y el servidor, y generalmente se serializa y pasa del servidor al cliente. Si no se proporciona esta función, la representación del componente en el servidor generará un error.
Devuelve
La instantánea actual de la fuente de almacenamiento externa de datos que puede usar en su lógica de representación.
Advertencias
-
La instantánea de la fuente de almacenamiento externa de datos devuelta por
getSnapshot
debe ser inmutable. Si el almacén subyacente tiene datos mutables, devuelva una nueva instantánea inmutable si los datos han cambiado. De lo contrario, devuelva la última instantánea almacenada en caché. -
Si se pasa una función
subscribe
diferente durante un re-renderizado, React se volverá a suscribir a la fuente de almacenamiento externa de datos usando la funciónsubscribe
recién pasada. Puede evitar esto declarandosubscribe
fuera del componente.
Uso
Subscribiéndose a una fuente de almacenamiento datos externa
Normalmente la mayoría de tus componentes de React solo leerán datos de sus propiedades,, estado, y contexto.. Sin embargo, a veces un componente necesita leer algunos datos de alguna fuente de almacenamiento fuera de React que cambia con el tiempo. Esto incluye:
- Bibliotecas de gestión de estado de terceros que mantienen el estado fuera de React.
- APIs del navegador que exponen un valor mutable y eventos para suscribirse a sus cambios.
Llama a useSyncExternalStore
en el nivel mas alto de tu componente para leer un valor de una fuente de datos externa.
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
// ...
}
Esto devuelve la instantánea del dato en la fuente de almacenamiento de datos. Necesitas pasar dos funciones como argumentos:
- La función
subscribe
deberá suscribirse a la fuente de almacenamiento de datos y devolver una función que permita des suscribirse. - La función
getSnapshot
deberá obtener una instantánea del dato de la fuente de almacenamiento de datos.
React utilizará estas funciones para mantener tu componente suscrito a la fuente de almacenamiento de datos y volver a renderizarlo con los cambios.
Por ejemplo, en el sandbox de a continuación, todosStore
se implementa como una fuente de almacenamiento de datos externa que almacena datos fuera de React. El componente TodosApp
se conecta a esta fuente de almacenamiento externa de datos con el Hook useSyncExternalStore
.
import { useSyncExternalStore } from 'react'; import { todosStore } from './todoStore.js'; export default function TodosApp() { const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot); return ( <> <button onClick={() => todosStore.addTodo()}>Add todo</button> <hr /> <ul> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> </> ); }
Subscribiéndose a una API de navegador
Otra razón para usar useSyncExternalStore
es cuando desea suscribirse a algún valor expuesto por el navegador que cambia con el tiempo. Por ejemplo, suponga que desea que su componente muestre si la conexión de red está activa. El navegador expone esta información a través de una propiedad llamada navigator.onLine
. Este valor puede cambiar con el tiempo sin que React sea notificado, por lo que necesitas leerlo con useSyncExternalStore
.
import { useSyncExternalStore } from 'react';
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ...
}
Para implementar la función getSnapshot
, lee el valor actual de la API del navegador:
function getSnapshot() {
return navigator.onLine;
}
A continuación, debes implementar la función subscribe
. Por ejemplo, cuando navigator.onLine
cambia, el navegador activa los eventos online
y offline
en el objeto window
. Debe suscribir el argumento callback
a los eventos correspondientes y luego devolver una función que limpie estas suscripciones:
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
Ahora React sabe cómo leer el valor de la API navigator.onLine
externa y cómo suscribirse a sus cambios. Intente desconectar su dispositivo de la red y observe que el componente se vuelve a renderizar en respuesta:
import { useSyncExternalStore } from 'react'; export default function ChatIndicator() { const isOnline = useSyncExternalStore(subscribe, getSnapshot); return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; } function getSnapshot() { return navigator.onLine; } function subscribe(callback) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; }
Extrayendo la lógica en un Hook personalizado
Por lo general, no deberías escribir useSyncExternalStore
directamente en tus componentes. En su lugar, normalmente lo llamarás desde tu propio Hook personalizado. Esto te permite usar la misma fuente de almacenamiento externa desde diferentes componentes.
Por ejemplo, este Hook personalizado useOnlineStatus
monitoriza si la red está en línea:
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return isOnline;
}
function getSnapshot() {
// ...
}
function subscribe(callback) {
// ...
}
Ahora diferentes componentes pueden llamar a useOnlineStatus
sin repetir la implementación subyacente:
import { useOnlineStatus } from './useOnlineStatus.js'; function StatusBar() { const isOnline = useOnlineStatus(); return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; } function SaveButton() { const isOnline = useOnlineStatus(); function handleSaveClick() { console.log('✅ Progress saved'); } return ( <button disabled={!isOnline} onClick={handleSaveClick}> {isOnline ? 'Save progress' : 'Reconnecting...'} </button> ); } export default function App() { return ( <> <SaveButton /> <StatusBar /> </> ); }
Añadiendo soporte para la renderización en el servidor
Si su aplicación React usa renderización en el servidor,, sus componentes React también se ejecutarán fuera del entorno del navegador para generar el HTML inicial. Esto crea algunos desafíos cuando se conecta a una fuente de datos externa:
- Si se está conectando a una API unicamente de navegador, no funcionará porque no existe en el servidor.
- Si se está conectando a una fuente de datos externa de terceros, necesitará que sus datos coincidan entre el servidor y el cliente.
Para resolver estos problemas, pase una función getServerSnapshot
como tercer argumento a useSyncExternalStore
:
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
return isOnline;
}
function getSnapshot() {
return navigator.onLine;
}
function getServerSnapshot() {
return true; // Always show "Online" for server-generated HTML
}
function subscribe(callback) {
// ...
}
La función getServerSnapshot
es similar a getSnapshot
, pero solo se ejecuta en dos situaciones:
- Se ejecuta en el servidor al generar el HTML.
- Se ejecuta en el cliente durante la hidratación, es decir, cuando React toma el HTML del servidor y lo hace interactivo.
Esto le permite proporcionar la instantánea del valor inicial que se utilizará antes de que la aplicación se vuelva interactiva. Si no hay un valor inicial significativo para la representación del servidor, puede forzar el componente para que se renderize solo en el cliente.
Solución de problemas
Recibo un error: “El resultado de getSnapshot
debería almacenarse en caché”
Si obtiene este error, significa que su función getSnapshot
devuelve un nuevo objeto cada vez que se llama, por ejemplo:
function getSnapshot() {
// 🔴 Do not return always different objects from getSnapshot
return {
todos: myStore.todos
};
}
React volverá a rerenderizar el componente si el valor de retorno de getSnapshot
es diferente al de la última vez. Por eso, si siempre devuelves un valor diferente, entrarás en un bucle infinito y obtendrás este error.
Tu objeto getSnapshot
solo debería devolver un objeto diferente si algo realmente ha cambiado. Si tu fuente de almacenamiento de datos externa contiene datos inmutables, puede devolver esos datos directamente:
function getSnapshot() {
// ✅ You can return immutable data
return myStore.todos;
}
Si los datos de tu fuente de almacenamiento de datos externa son mutables, tu función getSnapshot
debería devolver una instantánea inmutable de la misma. Esto significa que sí necesita crear nuevos objetos, pero no debería hacer esto en cada llamada. En su lugar, debe almacenar la última instantánea calculada y devolver la misma instantánea que la última vez si los datos almacenados no han cambiado. La forma en que determina si los datos mutables han cambiado depende de cómo se implemente tu fuente de almacenamiento de datos externa mutable.
Mi función subscribe
se llama después de cada re-renderizado
Esta función subscribe
se define dentro de un componente, por lo que es diferente en cada re-renderizado:
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// 🚩 Siempre una función diferente, por lo que React se volverá a suscribir en cada re-renderizado
function subscribe() {
// ...
}
// ...
}
React se volverá a suscribir a su fuente de almacenamiento de datos externa si pasa una función de subscribe
diferente entre re-renderizaciones. Si esto causa problemas de rendimiento y desea evitar volver a suscribirse a la fuente de almacenamiento de datos externa, mueva la función subscribe
fuera:
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ...
}
// ✅ Siempre la misma función, por lo que React no necesitará volver a suscribirse
function subscribe() {
// ...
}
Alternativamente, puedes envolver subscribe
con useCallback
para solo re-suscribirte cuando algún argumento cambie:
function ChatIndicator({ userId }) {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ✅ Same function as long as userId doesn't change
const subscribe = useCallback(() => {
// ...
}, [userId]);
// ...
}