DEV Community

Cover image for How Tanstack improved my React App's performance and user experience.
Momin Mahmud
Momin Mahmud

Posted on

How Tanstack improved my React App's performance and user experience.

React is a great framework for building fast and interactive web applications, but it also comes with some challenges when it comes to data fetching and caching. How do we avoid unnecessary requests, stale data, loading states, and error handling? How do we optimize our app for performance and user experience?

That's where TanStack comes in. TanStack is a collection of open-source libraries that help you manage your application state with ease. In this blog post, I will share how I used three of TanStack's libraries - Query, Mutation, and Infinite Query - to improve my React app's performance and user experience.

Query

Query is a library that lets you fetch, cache, and update data in your React components with minimal code. It handles all the complex logic of data fetching, such as deduping, caching, refetching, background updates, and more. It also provides hooks to access the data and its status, such as loading, error, and success.

I used Query to fetch data from my API using the useQuery hook. This hook accepts a query key and a fetch function, and returns an object with the data and the status. For example, here is how I fetched a list of posts from my API:

import { useQuery } from '@tanstack/query'

function Posts() {
  const { data, status } = useQuery('posts', () => fetch('/api/posts').then(res => res.json()))

  if (status === 'loading') {
    return <div>Loading...</div>
  }

  if (status === 'error') {
    return <div>Error!</div>
  }

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {data.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

As you can see, Query makes it easy to fetch and display data in a declarative way. It also automatically caches the data, so if I navigate away from the Posts component and come back, it will show the cached data instantly, while also refetching the data in the background to keep it fresh. This improves the performance and user experience of my app, as the user doesn’t have to wait for the data to load every time.

Mutation

Mutation is a component that lets you perform mutations, such as creating, updating, or deleting data, in your React components. It handles all the complex logic of mutations, such as optimistic updates, error handling, and invalidation. It also provides hooks to access the mutation status and data, such as loading, error, and success.

I used Mutation to perform mutations on my data using the useMutation hook. This hook accepts a mutation function and returns an object with the mutation status and data, as well as a function to trigger the mutation. For example, here is how I created a new post using my API:

import { useMutation, useQueryClient } from '@tanstack/mutation'

function CreatePost() {
  const [title, setTitle] = useState('')
  const queryClient = useQueryClient()
  const { mutate, status } = useMutation(
    title => fetch('/api/posts', {
      method: 'POST',
      body: JSON.stringify({ title }),
      headers: {
        'Content-Type': 'application/json'
      }
    }).then(res => res.json()),
    {
      // Optimistically update the cache with the new post
      onMutate: newPost => {
        queryClient.setQueryData('posts', oldPosts => [...oldPosts, newPost])
      },
      // Invalidate the posts query to refetch the data
      onSuccess: () => {
        queryClient.invalidateQueries('posts')
      }
    }
  )

  const handleSubmit = e => {
    e.preventDefault()
    mutate(title)
    setTitle('')
  }

  return (
    <div>
      <h1>Create Post</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={title}
          onChange={e => setTitle(e.target.value)}
        />
        <button type="submit">Submit</button>
      </form>
      {status === 'loading' && <div>Creating post...</div>}
      {status === 'error' && <div>Error!</div>}
      {status === 'success' && <div>Post created!</div>}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

As you can see, Mutation makes it easy to perform and display mutations in a declarative way. It also allows me to optimistically update the cache with the new post, so the user can see the result immediately, while also invalidating the posts query to refetch the data from the server. This improves the performance and user experience of my app, as the user doesn’t have to wait for the mutation to complete to see the effect.

Infinite Query

Infinite Query is a component that lets you fetch and display paginated or infinite data in your React components. It handles all the complex logic of pagination, such as fetching more data, scrolling, and caching. It also provides hooks to access the data and its status, such as loading, error, and success.

I used Infinite Query to fetch and display infinite data from my API using the useInfiniteQuery hook. This hook accepts a query key and a fetch function, and returns an object with the data and the status, as well as functions to fetch more data and reset the data. For example, here is how I fetched and displayed an infinite list of comments from my API:


import { useInfiniteQuery } from '@tanstack/infinite-query'

function Comments() {
  const { data, status, fetchNextPage, hasNextPage, reset } = useInfiniteQuery(
    'comments',
    ({ pageParam = 0 }) => fetch(`/api/comments?page=${pageParam}`).then(res => res.json()),
    {
      // Get the next page parameter from the last page
      getNextPageParam: lastPage => lastPage.nextPage
    }
  )

  const handleScroll = e => {
    // Check if the user has scrolled to the bottom of the container
    const { scrollTop, scrollHeight, clientHeight } = e.target
    if (scrollTop + clientHeight >= scrollHeight) {
      // Fetch the next page if it exists
      if (hasNextPage) {
        fetchNextPage()
      }
    }
  }

  const handleReset = () => {
    // Reset the data to the first page
    reset()
  }

  if (status === 'loading') {
    return <div>Loading...</div>
  }

  if (status === 'error') {
    return <div>Error!</div>
  }

  return (
    <div>
      <h1>Comments</h1>
      <div style={{ height: '300px', overflow: 'auto' }} onScroll={handleScroll}>
        {data.pages.map(page => (
          <ul key={page.page}>
            {page.comments.map(comment => (
              <li key={comment.id}>{comment.text}</li>
            ))}
          </ul>
        ))}
      </div>
      <button onClick={handleReset}>Reset</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

As you can see, Infinite Query makes it easy to fetch and display infinite data in a declarative way. It also automatically caches the data, so if I navigate away from the Comments component and come back, it will show the cached data instantly, while also re-fetching the data in the background to keep it fresh. It also provides a way to fetch more data when the user scrolls to the bottom of the container and to reset the data to the first page. This improves the performance and user experience of my app, as the user can access the data in a fast and intuitive way.

Conclusion

In this blog post, I showed how I used TanStack to improve my React app’s performance and user experience. TanStack is a powerful and easy-to-use solution for data fetching and caching in React. It offers many features and benefits, such as:

  • Declarative and minimal code
  • Automatic and smart caching
  • Background updates and refetching
  • Optimistic updates and invalidation
  • Pagination and infinite data
  • Loading, error, and success states
  • And more!

Top comments (0)