Data inicial y de ejemplo en React Query
Este artículo es una traducción del post "Placeholder and Initial Data in React Query" publicado por Dominik en su blog TkDodo.eu
El artículo de hoy va sobre mejorar la experiencia del usuario al trabajar con React Query.
La mayor parte del tiempo no nos hacen mucha gracia esos marcadores giratorios de “Cargando…“. Son necesarios algunas veces, pero queremos evitarlos siempre que sea posible.
Y React Query ya nos da las herramientas necesarias para librarnos de ellos en la mayoría de situaciones:
- Recibimos data desactualizada desde la caché mientras en el background se está actualizando.
- Podemos pre-solicitar la data si sabemos que la necesitaremos luego.
- Incluso podemos preservar la data previa cuando nuestras
query key
s cambian y queremos evitar esos estados de carga tan bruscos.
Otra forma es precargar la caché de forma síncrona con la data que pensemos que será correcta para nuestro caso, y para eso React Query ofrece dos enfoques distintos pero parecidos: Data de ejemplo (placeholder) y Data inicial.
Vamos a empezar con lo que ambas formas tienen en común, y luego exploraremos sus diferencias y las situaciones donde una será mejor que la otra.
Similitudes
Como ya hemos comentado, ambos enfoques ofrecen una forma de precargar la caché con data que tenemos disponible de forma síncrona.
Esto significa que si proveemos alguna de estas opciones nuestra solicitud no estará nunca en estado loading
, y pasará directamente a success
.
Además, ambas pueden ser tanto un valor como una función que devuelva un valor, para las ocasiones donde calcular ese valor sea costoso:
function Component() {
// 🟢 "status" será "success" aunque todavía no hayamos recibido la data
const { data, status } = useQuery({
queryKey: ['number'],
queryFn: fetchNumber,
placeholderData: 23,
});
// 🟢 lo mismo usando initialData y una función
const { data, status } = useQuery({
queryKey: ['number'],
queryFn: fetchNumber,
initialData: () => 42,
});
}
Por último, ninguna de las dos tiene ningún efecto si ya tienes data en tu caché.
Por eso… ¿qué diferencia hay entre usar una o la otra?
Inciso: opciones de una query
Para entenderlo, primero tenemos que mirar en un momento cómo (o a qué nivel) trabajan las opciones de una solicitud en React Query:
A nivel caché
Por cada query key
existe una única entrada en el caché. Esto es más o menos obvio, porque parte de lo que hace genial a React Query es la posibilidad de compartir data globalmente en nuestra aplicación.
Algunas opciones que pasemos a useQuery
afectarán a esta entrada en el caché, por ejemplo staleTime
o cacheTime
.
Como solo existe una única entrada, esas opiones especifican cuándo esta es considerada desactualizada, o cuándo puede ser recolectada.
A nivel observador
Un observador en React Query es, por encima, una suscripción creada para una entrada en el caché. El observador revisa esa entrada y será informado cada vez que algo cambie.
La forma más básica de crear un observador es llamar a useQuery
. Cada vez que lo hagamos, creamos un observador, y nuestro componente se re-renderizará cuando la data cambie. Esto por supuesto significa que podemos tener múltiples observadores controlando la misma entrada de caché.
Por cierto, puedes ver cuántos observadores tiene una solicitud en el número a la izquierda de la query key
en las Herramientas del Desarrollador de React Query (3 en este ejemplo):
Algunas opciones que trabajan a nivel observdador son select
o keepPreviousData
.
De hecho, lo que hace select
tan bueno para transformaciones de data es la habilidad para observar la misma entrada de caché, pero suscibirse a diferentes porciones de esta data en cada componente.
Diferencias
Básicamente, initialData
trabaja a nivel caché, mientras que placeholderData
actúa a nivel observador. Esto tiene un par de consecuencias:
Persistencia
Para empezar, initialData
es persistida en caché. Es una forma de decirle a React Query: Tengo data buena para este caso, data que es tan buena como si la hubieras solicitado desde el backend.
Como funciona a nivel caché, solo puede haber una initialData
, y esa data será puesta en caché tan pronto como esa entrada sea creada (cuando el primer observador sea montado). Si tratas de montar un segundo observador con initialData
diferente, no hará nada.
Por otro lado, placeholderData
no se persiste en caché nunca. Es una data de tipo provisional. No es real. React Query te lo ofrece para que puedas mostrar algo mientras la data real se está solicitando.
Como funciona a nivel observador, teóricamente podrías hasta tener diferentes placeholderData
en diferentes componentes.
Re-solicitudes de fondo
Con placeholderData
siempre tendrás una re-solicitud de fondo (background refetch) cuando montes un observador la primera vez. Como la data no es real, React Query conseguirá la data real por ti.
Mientras esto ocurre, también obtendrás un indicador isPlaceholderData
devuelto desde useQuery
. Puedes usar este marcador para avisar visualmente a tus usuarios de que la data que están viendo es en realidad data de ejemplo. Volverá a false
en cuanto la data real te llegue.
Con initialData
, como la data es tan válida como cualquier otra que pondríamos en caché, se respeta staleTime
. Si tienes un staleTime
de 0 (valor por defecto), verás de todas formas una re-solicitud de fondo.
Pero si ajustas staleTime
(por ejemplo 30 segundos) en tu query
, React Query verá la data inicial y pensará:
Oh, ya estoy recibiendo data nueva síncronamente, muchas gracias, no necesito ir al backend porque esta data es válida para los próximos 30 segundos.
Si esto no es lo que quieres, puedes pasar initialDataUpdatedAt
a tu query. Esto le dirá a React Query cuándo se ha creado esta data inicial, y las re-solicitudes de fondo se llamarán teniendo esto en cuenta.
Esto es muy útil cuando uses data inicial desde una entrada de caché existente, usando la marca de tiempo dataUpdatedAt
disponible:
const useTodo = (id) => {
const queryClient = useQueryClient();
return useQuery({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
staleTime: 30 * 1000,
initialData: () =>
queryClient
.getQueryData(['todo', 'list'])
?.find((todo) => todo.id === id),
initialDataUpdatedAt: () =>
// 🟢 re-solicitará en el background
// si nuestra data es más antigua que "staleTime" (30 segundos)
queryClient.getQueryState(['todo', 'list'])?.dataUpdatedAt,
});
}
Gestionar los errores
Imagina que usas initialData
o placeholderData
, se ejecuta una re-solicitud de fondo, y esta falla. ¿Qué crees que pasaría en cada caso?
He escondido las soluciones para que lo pienses primero, antes de expandirlas 😉
Con initialData
Como initialData
es persistida al caché,
el error en la re-solicitud es tratado como cualquier otro error en el background.
Nuestra query estará en estado de error
,
pero nuestra data
todavía estará ahí.
Usando placeholderData
Como placeholderData
es data irreal, y algo no ha funcionado,
ya no veremos ninguna data. Nuestra query estará en estado de error
,
y nuestra data
será undefined
.
Cuál usar cuándo
Como siempre, depende de lo que prefieras. Personalmente uso initialData
cuando estoy precargando una query a partir de otra, y placeholderData
para todo lo demás.
La serie completa
Este post es parte de la serie React-Query por Tkdodo que he traducido desde su blog. Mira todos los artículos:
- Consejos prácticos sobre React Query
- Transformación de data en React Query
- Optimización del renderizado en React Query
- Comprobar el estado en React Query
- Tests en React Query
- React Query y TypeScript
- Usar Websockets con React Query
- Claves eficaces en React Query
- Data inicial y de ejemplo en React Query
- React Query como un Gestor de Estado
No hay sección de comentarios, pero me encantaría escuchar tu opinión: escríbeme en Twitter y cuéntame!