Getting started with React Query - Easy server state management in React
Tags: webdev, react, redux, state
Published: 2020-04-24 05:21:54

## Getting started

Handling state, which comes from the server can really cause some headaches in React. There is a lot you have to think about when dealing with asynchronous data, like updating, caching, or re-fetching to name a few.

This is where [react-query](https://www.npmjs.com/package/react-query) comes in. It handles those smoothly and also offers simple solutions for optimistic rendering, infinite scroll, pagination, and more.

Here's a small demo of what we'll be building:

![blog application with smooth loading](https://wweb.dev/blog/react-query/react-query-demo.gif?)

If you'd like to jump straight into the code, you can find the repository here:  
[https://github.com/wwebdev/react-query-demo](https://github.com/wwebdev/react-query-demo)

For this tutorial, I'll assume you have node installed. First of all, create a new react app with `npx create-react-app`. Afterward, install react-query with `npm i --save react-query`

For demonstrating how react-query works I'll use the [Json Placeholder API](https://jsonplaceholder.typicode.com/) to create a simple blog.

## Fetching the data

First of all, I'll remove all the boilerplate code in the App.js and replace it with following code:

```javascript
import React from 'react';
import { useQuery } from 'react-query'

const getPosts = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts')
  return response.json()
}

function App() {
  const { status, data, isFetching, error } = useQuery('posts', getPosts)

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

  if (status === 'error') {
    return <div>{error.message}</div> // error state
  }

  return (
    <div>
      { data && <ul>{
        data
          .slice(0,10) // only take frist 10 for now
          // render list of titles
          .map(d => <li key={`post-${d.id}`}>{d.title}</li>)
      }</ul> }
      { isFetching && <p>updating...</p> }
    </div>
  )
}

export default App
```

First I define a function called `getPosts` - this can include anything as long as it's returning an asynchronous function.

In the beginning of the `App()` the useQuery hook is called with an identifier for the data that's being fetched as well as out async function getPosts.

The hook returns status, data, isFetching, and error. Those are pretty self-explanatory. The status can be either "success", "loading" or "error". The rest of the component handles the display of the result on the three possible states.

The internals of react-query will now take care of all the caching and updating logic. This means whenever you go to this page you'll know that the displayed data will be there instantly if you've previously fetched it and it will be always up to date with the server state.

That's actually all you need to know to get started using react-query. But let's extend this example to see this caching and updating in action!

## Extending the application

First of all, I'll move the code from App.js to a new component `components/Home.js`. Therefore I will rename the component and I'll also add a `NavLink` to the post list.

```javascript
import React from 'react'
import { NavLink } from 'react-router-dom'
import { useQuery } from 'react-query'

const getPosts = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts')
  await new Promise(r => setTimeout(r, 1000)) // wait a second
  return response.json()
};

function Home() {
  const { status, data, isFetching, error } = useQuery('posts', getPosts)

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

  if (status === 'error') {
    return <div>{error.message}</div> // error state
  }

  return (
    <div>
      { data && <ul>{
        data
          .slice(0,10) // only take frist 10 for now
          .map(d => <li key={`post-${d.id}`}>
            <NavLink to={`/post/${d.id}`}>{d.title}</NavLink>
          </li>) // render list of titles
      }</ul> }
      { isFetching && <p>updating...</p> }
    </div>
  );
}

export default Home
```

Now let's add a router to App.js, which accepts the routes to `/` for Home.js and `/post/:id` for a single post page.

```javascript
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'

import Home from './components/Home'
import Post from './components/Post'

function App() {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path = '/post/:id' render = {routerProps => <Post id={routerProps.match.params.id}/>} />
      </Switch>
    </Router>
  )
}

export default App
```

And finally, I'll create a new component `components/Post.js` for displaying the data of a single post. The explanation will follow after the code.

```javascript
import React from 'react'
import { NavLink } from 'react-router-dom'
import { useQuery } from 'react-query'

const Post = ({ id }) => {
  const getPost = async () => {
    const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
    const jsonResponse = await response.json()
    jsonResponse.title = `${jsonResponse.title} - ${Math.random().toString(36)}`

    await new Promise(r => setTimeout(r, 1000)) // wait a second
    return jsonResponse
  }

  const { status, data, isFetching } = useQuery(`post-${id}`, getPost)

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

  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.body}</p>
      { isFetching && <p>updating...</p> }
      <br />
      <NavLink to="/">Home</NavLink>
    </div>
  )
}

export default Post
```

So the `useQuery` in here doesn't differ much from the one in Home.js. It adds the `id` to the identifier, so each post has its own state. Additionally, I've added a timeout for 1 second to the `getPost` function to make the loading state more visible. Also, I've appended a random string to the title to make the re-fetching visible.

And that's actually the whole code for the gif you saw at the beginning of the post.

If you start working with react-query, I'd recommend you to check out the [react-query-devtools](https://github.com/tannerlinsley/react-query-devtools), to be able to view the state and the cache.

Feel free to check out the code on [GitHub](https://github.com/wwebdev/react-query-demo). Also let me know if you'd like to know more about the usage of react-query for things like `initial data`, `pre-fetching`, `optimistic rendering` and I'll extend this to a series of posts.