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?)

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:

  1. La función subscribe deberá suscribirse a la fuente de almacenamiento de datos y devolver una función que permita des suscribirse.
  2. 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>
    </>
  );
}

Nota

Cuando sea posible, recomendamos usar el control de estado incorporado en React con useState y useReducer en su lugar. La useExternalSyncStore API is principalmente útil si necesita integrarse con código existente que no sea de React.


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.

Nota

Asegúrate de que getServerSnapshot devuelva exactamente los mismos datos en el renderizado inicial del cliente que en el que es devuelto en el servidor. Por ejemplo, si getServerSnapshot devolvió algún contenido prepopulado de la fuente de almacenamiento externa en el servidor, debes transferir este contenido al cliente. Una forma común de hacer esto es emitir una etiqueta <script> que establece una propiedad global como window.MY_STORE_DATA durante la renderización del servidor, y que permitirá poder leer esa propiedad global desde el cliente en getServerSnapshot. Tu fuente de almacenamiento externa debería proporcionar instrucciones sobre cómo hacer esto.


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:

  1. La función subscribe debe suscribirse a la fuente de almacenamiento de datos externa y devolver una función que cancela dicha suscripción.
  2. La función getSnapshot debería obtener una instantánea de los datos de la fuente de almacenamiento de datos externa.

Ver más ejemplos arriba.

Parametros

  • subscribe: Una función que toma un solo argumento callback y lo suscribe a la fuente de almacenamiento de datos externa. Cuando la fuente de almacenamiento de datos externa cambia, debe invocar el callback proporcionado. Esto hará que el componente se vuelva a rerenderizar. La función subscribe 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 a getSnapshot deben devolver el mismo valor. Si la fuente de almacenamiento externa de datos cambia y el valor devuelto es diferente (usando para la comparación Object.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ón subscribe recién pasada. Puede evitar esto declarando subscribe fuera del componente.


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 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]);

// ...
}