Skip to content

¿Por qué deberías usar React Query o SWR?

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

La mayoría de nosotros aquí en React-land estamos desarrollando nuestras aplicaciones con la nueva y brillante API Hooks y enviando peticiones a APIs externas como nadie. Quienes somos nuevos con los hooks, puede que hayamos empezado a crearlos como el siguiente ejemplo simplificado:

export const useSearch = () => {
    const [query, setQuery] = useState();
    const [results, setResults] = useState();

    useEffect(() => {
        if (!query)
            return;

        api.search(query)
            .then(results => setResults(results));
    }, [query]);

    return {
        query,
        setQuery,
        results
    }
}

El Problema

Sin embargo, cada vez que se invoca a un hook, esa instancia es única para el componente desde el que se llamó, por lo que podemos encontrar algunos problemas:

  1. Actualizar la consulta anterior en una instancia no la actualizará para las demás.
  2. Si tenemos tres componentes que utilizan un hook que realiza una petición a un API, obtendremos como resultado al menos una solicitud por cada componente.
  3. Si tenemos varias peticiones en el aire, e intentamos almacenarlas en un store global o si el hook mantiene el estado, terminaremos con un estado fuera de sincronización o peticiones posteriores sobrescribiéndose entre sí.

Una forma de resolver esto es dejar la petición fuera de los hooks y solo ejecutarla en un componente que puede garantizar que será una instancia única(también conocido como un componente singleton, como una página/ruta tal vez). Dependiendo de cómo se usen esos datos, esto a veces puede ser complejo de manejar.

Posibles Soluciones

Entonces, ¿qué podemos hacer? He aquí algunas opciones:

  1. Asegúrate de que los datos que provienen de tu API vayan en contexto o por medio de algún tipo de manejo de estado global, aceptando las múltiples peticiones (potencialmente sobrecargando la API de nuestro servidor)
  2. Al hacer lo anterior + usando una librería como react-singleton-hook, asegúrate de que solo haya un componente con useEffect haciendo la llamada al API, o similar para evitar múltiples peticiones.
  3. Implementar algún tipo de caché de datos (mientras sea posible invalidarlo según sea necesario) para que podamos extraer los datos primero desde esa caché.
  4. Usar React Query o SWR

La Solución Real

La solución real aquí es usar la opción 4. Ambos paquetes han implementado una solución sofisticada para resolver estos problemas y evitar que tengas que hacerlo por ti mismo. El almacenamiento en caché es complejo de implementarlo "bien" y podría tener consecuencias inesperadas si se hace mal, que podría derivar en una aplicación "rota" parcial o totalmente.

Otros Problemas Resueltos

A continuación, se muestran algunos ejemplos de otros problemas que estos paquetes pueden resolver. Se muestran ejemplos de código para cada uno(se usa React Query pero es similar a SWR).

Recuperación de Foco en Ventana(Window Focus Refetching)

Un gran problema que encontramos usualmente con sitios y aplicaciones grandes con JavaScript, es que un usuario puede estar en una pestaña o ventana del browser manipulando datos y luego cambiar a otra de la misma aplicación. El problema aquí es que si no mantenemos nuestros datos actualizados, estos pueden no estar sincronizados. Ambos paquetes resuelven esto volviendo a obtener datos una vez que la ventana tiene el foco activo nuevamente. Si no necesitas este comportamiento, simplemente puedes deshabilitarlo como opción.

    const { data: syncedData } = useQuery(id, id => getSyncedData(id), {
        refetchOnWindowFocus: true /* No necesita especificarse, se activa por defecto */
    })

Reintentar Peticiones, Revalidación y Polling

A veces una petición falla temporalmente, sucede. Ambos paquetes resuelven este problema permitiendo la configuración de reintentos automáticos, por lo que cada vez que detecten un error, reintentarán la cantidad de veces especificada hasta que finalmente retorne un error. Además, puedes usar cualquiera de las dos opciones para hacer un poll de un endpoint de manera constante simplemente estableciendo un intervalo de milisegundos para un refetch/refresh.

Ejemplo de Reintento(Retry)

    const { data: books } = useQuery(id, id => getBooks(id), {
        retry: 5, //intentar 5 veces antes de fallar nuevamente
        retryDelay: 1000 //intentar cada segundo
    })

    /* valores por defecto: {
        retry: 3,
        retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000)
    }*/

Ejemplo de Polling

    const indexing = useRef(false);
    const { data: searchResults } = useQuery(['search', keyword], (key, keyword) => search(keyword), {
        //obtener datos cada segundo hasta que se haya finalizado el "indexing"
        refetchInterval: indexing.current ? 1000 : undefined,
        //invalidar la caché de consultas después que esta sea exitosa,
        //hasta que se deje de indexar
        onSuccess: async data => {
            if (data.indexing) {
                await queryCache.invalidateQueries(keyword)
            }
        }
    });

    //actualizar nuestra referencia 
    indexing.current = !!searchResults?.indexing;

Mutación con Actualizaciones Optimistas

Supongamos que tienes una lista de usuarios y deseas actualizar la información de uno de ellos, una operación bastante habitual. La mayoría de los usuarios estarán contentos al ver un indicador de progreso cuando el servidor esté trabajando para actualizar a dicho usuario y esperarán a que termine antes de ver la lista actualizada.

Sin embargo, si sabemos cómo se verá la lista de usuarios actualizada localmente (porque el usuario acaba de realizar dicha acción), ¿Realmente necesitamos mostrar un cargador? No, ambos paquetes permiten hacer mutaciones(cambios) en los datos guardados en caché que se actualizarán de inmediato localmente e iniciarán la actualización en el servidor en segundo plano. También se asegurarán de que los datos se vuelvan a recuperar/validar para que sean los mismos una vez se obtenga la respuesta del servidor, y si no, los datos devueltos estarán en su lugar.

Imagina que tenemos una página donde el usuario puede editar su información. Primero, necesitamos obtener los datos del backend.

    const cache = useQueryCache()
    const userCacheKey = ['user', id];
    const { data: user } = useQuery(userCacheKey, (key, id) => {
        return fetch(`/user/${id}`).then(res => res.json());
    });

A continuación, necesitamos configurar una función que permita actualizar los datos del usuario una vez que estos se envíen desde el formulario.

    const [updateUser] = useMutation(
        newUser => fetch(`/user/${id}`, {
            method: 'POST',
            body: JSON.stringify(newUser)
        }).then(res => res.json()),
        ...

Si queremos que nuestros datos se mantengan actualizados con la interfaz de usuario(UI) de manera optimista, se tiene que agregar algunas opciones para dicha mutación. El atributo onMutate establecerá los datos localmente en el caché antes de la actualización real para que la interfaz de usuario no muestre un indicador de progreso. El valor de retorno se usa en caso de error y necesitaremos restablecer al estado anterior.

    onMutate: newUser => {
        cache.cancelQueries(userCacheKey)

        const oldData = cache.getQueryData(userCacheKey)
        cache.setQueryData(userCacheKey, newUser)

        return oldData
    }

Si estamos actualizando de manera optimista, debemos poder manejar los posibles errores y también asegurarnos de que el servidor devuelva los datos esperados. Entonces es necesario agregar dos hooks más a las opciones de mutación. onError usará los datos devueltos por onMutate para que sea factible restablecer el estado anterior. En cambio, onSettled asegura que se vaya a obtener los mismos datos del servidor para que todo esté sincronizado.

    //Reestablece los datos previos cuando surge un error
    onError: oldUser => {
        cache.setQueryData(userCacheKey, oldUser)
    },
    onSettled: () => {
        cache.invalidateQueries(userCacheKey)
    }

Prefetching y Fetching en Segundo Plano

Si se tiene una idea sobre algunos datos que el usuario podría necesitar, se puede usar estos paquetes para obtener esos datos con antelación(prefetch). Para cuando el usuario llega, los datos ya están listos, lo que hace que la transición sea instantánea. Esta implementación puede hacer que tu aplicación se sienta más ligera.

    const prefetchUpcomingStep = async (stepId) => {
        await cache.prefetchQuery(stepId, stepId => fetch(`/step/${stepId}`))
    }

    //más tarde...

    prefetchUpcomingStep('step-137')
    //esto permite obtener los datos antes de llegar a la consulta misma

Como nota adicional, si el usuario ya ha recibido datos, pero es hora de actualizar, los paquetes buscarán obtenerlos en segundo plano y reemplazarán los datos antiguos si y solo si son diferentes. Esto evita mostrar al usuario un indicador de progreso, notificando únicamente si hay algo nuevo, lo que mejora la experiencia de usuario.

Imagina que tenemos un componente que lista novedades al estilo de Twitter y que constantemente está recibiendo nuevas publicaciones.

    const Feed = () => {
        const { data: feed, isLoading, isFetching } = useQuery(id, id => getFeed(id), {
            refetchInterval: 15000
        });

        ...

Podemos notificar a los usuarios que los datos se están actualizando en segundo plano al "escuchar" que isFetching es true, que se activará incluso si hay datos de caché.


    <header>
        <h1>Your feed</h1>
        {
            isFetching && 
            <Notification>
                <Spinner /> loading new posts
            </Notification>
        }
    </header>

Si no se tiene ningún dato en la caché y la consulta está obteniendo datos, podemos escuchar por isLoading como true y mostrar algún tipo de indicador de progreso. Finalmente, si isSuccess es true y recibimos datos, podemos mostrar las publicaciones.

    <FeedContainer>
        {   
            isLoading && <LoadingCard /> 
        }
        {
            feed && isSuccess && feed.posts.map(post => (
                <Post {...post} />
            ))
        }
    </FeedContainer>

Comparación

El autor de React Query hizo un gran trabajo al crear una tabla de comparación para React Query, SWR y Apollo para que puedas ver qué funciones están disponibles. Una gran característica que me gustaría mencionar de React Query sobre SWR es su propio conjunto de herramientas de desarrollo que son realmente útiles para depurar consultas que puedan estar fallando.

Conclusión

Durante mi tiempo como desarrollador, he intentado resolver estos problemas por mí mismo, y si hubiera tenido un paquete como React Query o SWR, habría ahorrado mucho tiempo. Estos problemas pueden ser realmente un reto a resolver y una solución propia puede terminar inyectando errores sutiles en tu aplicación, que pueden ser difíciles de depurar o que requieren mucho tiempo. Afortunadamente, tenemos código abierto(open source) y estas personas fueron generosas al ofrecer sus soluciones robustas para nosotros.

Si te gustaría conocer más sobre los problemas que resuelven estos paquetes y el esfuerzo necesario para resolverlos, Tanner Linsley hizo un gran relato por lo que estuvo experimentando y cómo lo resolvió. Puedes ver su tutorial aquí:

En general, considero que estos paquetes son excelentes adiciones al ecosistema de desarrollo y nos ayudan a escribir mejor software. Me gustaría ver otros frameworks con opciones similares, porque los conceptos que se mencionan aquí son bastante habituales. Espero que esto te haya resultado útil y nos hagas saber alguna estrategia tuya al usar dichas opciones.

PD. ¿Qué hay de GraphQL? 😂

Bueno, muchas de los paquetes GraphQL que existen en realidad incorporaron estos conceptos desde el principio, por lo que si estás usando algo como Apollo o Urql, muy probablemente ya estés obteniendo estas ventajas. Sin embargo, ambas librerías son compatibles con cualquier función que retorne una Promesa, por lo que si tu librería GQL favorita no tiene estas características, intenta usar React Query o SWR. 😁

Este artículo es una traducción al español de su versión en inglés

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Incremental Hydration in Angular cover image

Incremental Hydration in Angular

Incremental Hydration in Angular Some time ago, I wrote a post about SSR finally becoming a first-class citizen in Angular. It turns out that the Angular team really treats SSR as a priority, and they have been working tirelessly to make SSR even better. As the previous blog post mentioned, full-page hydration was launched in Angular 16 and made stable in Angular 17, providing a great way to improve your Core Web Vitals. Another feature aimed to help you improve your INP and other Core Web Vitals was introduced in Angular 17: deferrable views. Using the @defer blocks allows you to reduce the initial bundle size and defer the loading of heavy components based on certain triggers, such as the section entering the viewport. Then, in September 2024, the smart folks at Angular figured out that they could build upon those two features, allowing you to mark parts of your application to be server-rendered dehydrated and then hydrate them incrementally when needed - hence incremental hydration. I’m sure you know what hydration is. In short, the server sends fully formed HTML to the client, ensuring that the user sees meaningful content as quickly as possible and once JavaScript is loaded on the client side, the framework will reconcile the rendered DOM with component logic, event handlers, and state - effectively hydrating the server-rendered content. But what exactly does "dehydrated" mean, you might ask? Here's what will happen when you mark a part of your application to be incrementally hydrated: 1. Server-Side Rendering (SSR): The content marked for incremental hydration is rendered on the server. 2. Skipped During Client-Side Bootstrapping: The dehydrated content is not initially hydrated or bootstrapped on the client, reducing initial load time. 3. Dehydrated State: The code for the dehydrated components is excluded from the initial client-side bundle, optimizing performance. 4. Hydration Triggers: The application listens for specified hydration conditions (e.g., on interaction, on viewport), defined with a hydrate trigger in the @defer block. 5. On-Demand Hydration: Once the hydration conditions are met, Angular downloads the necessary code and hydrates the components, allowing them to become interactive without layout shifts. How to Use Incremental Hydration Thanks to Mark Thompson, who recently hosted a feature showcase on incremental hydration, we can show some code. The first step is to enable incremental hydration in your Angular application's appConfig using the provideClientHydration provider function: ` Then, you can mark the components you want to be incrementally hydrated using the @defer block with a hydrate trigger: ` And that's it! You now have a component that will be server-rendered dehydrated and hydrated incrementally when it becomes visible to the user. But what if you want to hydrate the component on interaction or some other trigger? Or maybe you don't want to hydrate the component at all? The same triggers already supported in @defer blocks are available for hydration: - idle: Hydrate once the browser reaches an idle state. - viewport: Hydrate once the component enters the viewport. - interaction: Hydrate once the user interacts with the component through click or keydown triggers. - hover: Hydrate once the user hovers over the component. - immediate: Hydrate immediately when the component is rendered. - timer: Hydrate after a specified time delay. - when: Hydrate when a provided conditional expression is met. And on top of that, there's a new trigger available for hydration: - never: When used, the component will remain static and not hydrated. The never trigger is handy when you want to exclude a component from hydration altogether, making it a completely static part of the page. Personally, I'm very excited about this feature and can't wait to try it out. How about you?...

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers cover image

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers

Before she was a software developer at freeCodeCamp, Jessica Wilkins was a classically trained clarinetist performing across the country. Her days were filled with rehearsals, concerts, and teaching, and she hadn’t considered a tech career until the world changed in 2020. > “When the pandemic hit, most of my gigs were canceled,” she says. “I suddenly had time on my hands and an idea for a site I wanted to build.” That site, a tribute to Black musicians in classical and jazz music, turned into much more than a personal project. It opened the door to a whole new career where her creative instincts and curiosity could thrive just as much as they had in music. Now at freeCodeCamp, Jessica maintains and develops the very JavaScript curriculum that has helped her and millions of developers around the world. We spoke with Jessica about her advice for JavaScript learners, why musicians make great developers, and how inclusive communities are helping more women thrive in tech. Jessica’s Top 3 JavaScript Skill Picks for 2025 If you ask Jessica what it takes to succeed as a JavaScript developer in 2025, she won’t point you straight to the newest library or trend. Instead, she lists three skills that sound simple, but take real time to build: > “Learning how to ask questions and research when you get stuck. Learning how to read error messages. And having a strong foundation in the fundamentals” She says those skills don’t come from shortcuts or shiny tools. They come from building. > “Start with small projects and keep building,” she says. “Books like You Don’t Know JS help you understand the theory, but experience comes from writing and shipping code. You learn a lot by doing.” And don’t forget the people around you. > “Meetups and conferences are amazing,” she adds. “You’ll pick up things faster, get feedback, and make friends who are learning alongside you.” Why So Many Musicians End Up in Tech A musical past like Jessica’s isn’t unheard of in the JavaScript industry. In fact, she’s noticed a surprising number of musicians making the leap into software. > “I think it’s because music and code have a lot in common,” she says. “They both require creativity, pattern recognition, problem-solving… and you can really get into flow when you’re deep in either one.” That crossover between artistry and logic feels like home to people who’ve lived in both worlds. What the Tech Community Is Getting Right Jessica has seen both the challenges and the wins when it comes to supporting women in tech. > “There’s still a lot of toxicity in some corners,” she says. “But the communities that are doing it right—like Women Who Code, Women in Tech, and Virtual Coffee—create safe, supportive spaces to grow and share experiences.” She believes those spaces aren’t just helpful, but they’re essential. > “Having a network makes a huge difference, especially early in your career.” What’s Next for Jessica Wilkins? With a catalog of published articles, open-source projects under her belt, and a growing audience of devs following her journey, Jessica is just getting started. She’s still writing. Still mentoring. Still building. And still proving that creativity doesn’t stop at the orchestra pit—it just finds a new stage. Follow Jessica Wilkins on X and Linkedin to keep up with her work in tech, her musical roots, and whatever she’s building next. Sticker illustration by Jacob Ashley....

How to Update the Application Title based on Routing Changes in Angular cover image

How to Update the Application Title based on Routing Changes in Angular

Have you tried to update the document's title of your application? Maybe you're thinking that applying interpolation should be enough: ` That solution is not going to work since the element is outside of the scope of the Angular application. In fact, the root component of your app is within tag, and the title is part of the element. Luckily, Angular provides the Title service with the methods to read the current title of the application, and a setTitle(title) to update that value. However, what happens if you need to update the title on routing changes? Also, you may consider updating it on certain components for Analytics purposes. In this blog post, I'll explain step-by-step how to create a custom Title service to have full control over the title of the current HTML document for your application. Project Setup Prerequisites You'll need to have installed the following tools in your local environment: - Node.js. Preferably the latest LTS version. - A package manager. You can use either NPM or Yarn. This tutorial will use NPM. Creating the Angular Project Let's assume we'll need to build an application with the following routes as requirements: ` Now, let's create the project from scratch using the Angular CLI tool. ` This command will initialize a base project using some configuration options: - --routing. It will create a routing module. - --prefix corp. It defines a prefix to be applied to the selectors for created components(corp in this case). The default value is app. - --style css. The file extension for the styling files. - --skip-tests. it avoids the generations of the .spec.ts files, which are used for testing Creating the Modules and Components Once we got the initial structure of the app, we'll continue running the following commands to create a separate module for /home and /products, which are the main paths of the project: ` * The --routing flag can be using also along with ng generate module to create a routing configuration file for that module. Creating the Title Service Similar to the previous section, we will create a shared module to hold the Title service. Both can be generated with the following commands: ` * The --module app flag is used to "link" the brand new module to the pre-existing app.module.ts file. The Routing Configuration Open the app-routing.module.ts file, and create the initial routes. ` * By default, the application will redirect to the home path. * When the router loads the home path, a HomeComponent will be rendered. * The products path will be loaded using the _lazy loading_ feature. Pay attention to the data provided to the home path. It contains the configured title through pageTitle string. Next, open the products-routing.module.ts file to enable an additional configuration to load the _Products_ and the _Product Detail_ page. ` * The router will render the ProductsComponent by default when the path matches to /products. This route also defines custom data to be rendered as titles later. * When the path also adds an Id on /products/:id, the router will render the ProductDetailComponent. The Title Service Implementation It's time to implement the custom Title Service for our application. ` The above service implementation could be understood in just a few steps. * First, we'll need to make sure to inject the Router, ActivatedRoute and Title services in the constructor. * The title$ attribute contains the initial value for the title("Corp"), which will be emitted through a _BehaviorSubject_. * The titleRoute$ is an Observable ready to emit any pageTitle value defined in the current route. It may use the parent's _pageTitle_ otherwise. * The titleState$ is an Observable ready to _listen_ to either title$ or titleRoute$ values. In case incoming value is defined, it will call the Angular Title service to perform the update. * The getPageTitle method will be in charge of obtaining the pageTitle of the current route if it is defined or the title of the parent otherwise. Injecting the Title Service One easy way to apply the custom Title Service in the whole application is by updating the app.module.ts file and injecting it into the constructor. ` In that way, once the default component gets rendered, the title will be displayed as Corp - Home. If you click on _Go to Products_ link, then a redirection will be performed and the Title service will be invoked again to display Corp - Products at this time. However, we may need to render a different title according to the product detail. In this case, we'll show Corp - Product Detail - :id where the Id matches with the current route parameter. ` Let's explain the implementation of this component: * The constructor injects the ActivatedRoute and the custom TitleService. * The productId$ is the _Observable_ which is going to emit the Id parameter every time it changes in the URL. * Once the component gets initialized, we'll need to _subscribe_ to the productId$ _Observable_ and then emit a new value for the title after creating a new string using the id. That's possible through the titleService.title$.next() method. * When the component gets _destroyed_, we'll need to _unsubscribe_ from the productIdSubscription. We're ready to go! Every time you select a product, the ProductDetail component will be rendered, and the title will be updated accordingly. Live Demo and Source Code Want to play around with the final application? Just open the following link in your browser: https://luixaviles.github.io/angular-update-title. Find the complete angular project in this GitHub repository: angular-update-title-service. Do not forget to give it a star ⭐️, and play around with the code. Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work....

What does it actually look like to build software with AI today? Not in theory, but in practice. cover image

What does it actually look like to build software with AI today? Not in theory, but in practice.

What does it actually look like to build software with AI today? Not in theory, but in practice. At the Leadership Exchange, this was the question at the center of the Developer Panel, where leaders from across the industry unpacked what’s really changing inside engineering teams and what organizations need to do right now to keep up. The Developer Panel at the Leadership Exchange explored the cutting edge of AI in software engineering and examined what organizations should focus on today to prepare for the future. Moderated by Jeff Cross, Co-Founder & CEO at Nx, the panel featured Victor Savkin, Cofounder & CTO at Nx, Alex Sover, Vice President of Engineering at OpenAP, Brent Zucker, Senior Director of Engineering at Visa, and Jonathan Fontanez, AI Engineering Lead at This Dot Labs. Panelists shared insights into how AI is transforming the software development lifecycle and how teams can adopt tools effectively while preparing for organizational change. Panelists discussed emerging workflows, including CI-in-the-loop, agentic healing, and context engineering. They examined how validation, code reviews, and PRDs are evolving alongside AI capabilities and how teams are integrating external sources such as production traces to improve quality and reliability. The discussion also covered what the next generation of agentic tools might look like and how these capabilities will shape engineering practices in the near future. Adoption of AI comes with challenges. Teams often rely on plugins or extensions without foundational understanding, and individual contributors may fear displacement. Panelists emphasized that education, governance, and skill-building are essential for teams to manage AI agents effectively while maintaining quality. They also highlighted the need to standardize workflows and ensure organizational alignment to fully leverage AI capabilities. The conversation extended beyond technical challenges to organizational implications. Panelists discussed how teams can avoid issues like Conway’s Law, manage distributed teams effectively, and evolve engineering practices alongside AI adoption. Leadership and management strategies play a crucial role in ensuring that AI integration delivers meaningful outcomes while maintaining efficiency and alignment with business objectives. Key Takeaways - AI workflows require both technical and organizational preparation. - Education, governance, and skill development are essential for successful implementation. - Forward-looking teams are rethinking validation, CI pipelines, and context management to fully leverage agentic AI. The discussion highlighted that adopting AI at the cutting edge is not just about new tools - it is about rethinking processes, workflows, and organizational culture. Companies that embrace this holistic approach are most likely to succeed in leveraging AI to its full potential. Are you interested in more conversations like this? Message us for an invite to the next, or for a private discussion around these topics. Tracy can be reached at tlee@thisdot.co....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co