How to move to react-query v1
Tags: react, javascript, opensource, webdev
Published: 2020-03-02 01:29:33

`react-query` v1 is here! And with it comes several breaking changes that will render your code useless if you try to upgrade without making any changes. This guide will cover several examples of old code using `react-query` v0.4.3 or older, and how to get the same functionality out of v1.0.*.

Firstly, if you are unfamiliar with [react-query](https://github.com/tannerlinsley/react-query/), this guide won't be too useful for you. Instead, I recommend jumping right into the Readme if you want a simple, hook-based library to help you make API calls.

If you do have code using an older version, however, look no further!

## Pagination

We're going to start with pagination, as this is the first breaking change in v1. Gone is the `paginated` flag on the `useQuery` hook. Instead, there is a new `usePaginatedQuery` hook.

```jsx
  const {
    status,
    resolvedData,
    latestData,
    error,
    isFetching,
  } = usePaginatedQuery(['projects', page], fetchProjects);
```

As you can see, the code is fairly similar, and we still are able to find out if the query is still running via the `isFetching` status, but there is no longer an `isFetchingMore` function to call as we are only receiving one page's worth of data at a time here.

`data` does not exist when using this hook, however. Now, there are two different objects that contain data, `resolvedData` and `latestData`:

1. `resolvedData` is data from the last known successful query result at first, and will remain the same until the next query successfully resolves.

1. `latestData` is only ever the data from the latest query, so it will be `undefined` until the latest query resolves.

Both objects can be mapped over similarly to how one might use data, like this:

```jsx
<div>
  {resolvedData.projects.map(project => (
    <p key={project.id}>{project.name}</p>
  ))}
</div>
```

Other than that, we are just syntactically telling the library to do a paginated query in a different way: specifying the hook instead of having a more generic one where we pass in whether things are paginated or not. 

This makes the resulting code a little cleaner in my opinion than how we used to do it, which involved the same generic `useQuery` hook that was available in v0.4.3, and telling it that it was `paginated` like this:

```jsx
const { data,
    isLoading,
    isFetching,
    isFetchingMore,
    fetchMore,
    canFetchMore,
  } = useQuery(
    'projects',
    ({ nextId } = {}) => fetch('/api/projects?page=' + (nextId || 0)),
    {
      paginated: true,
      getCanFetchMore: lastPage => lastPage.nextId,
    }
  );
```

## Infinite Loading

Previously, if you wanted to load more, you had to implement a `loadMore` function like so:

```jsx
const loadMore = async () => {
    try {
      const { nextId } = data[data.length - 1];

      await fetchMore({
        nextId,
      });
    } catch {}
  };
```

While this is still the case if you want to manually control which pages you load and when it is no longer necessary to continuously call a function like that if you want to implement an infinite load to get all of the resources from your API.

The `useInfiniteQuery` is aptly named to help you get that job done, all while providing an interface similar to that of the old`useQuery`:

```jsx
const {
    status,
    data,
    isFetching,
    isFetchingMore,
    fetchMore,
    canFetchMore,
  } = useInfiniteQuery('projects', fetchProjects, {
    getFetchMore: (lastGroup, allGroups) => lastGroup.nextCursor,
  });
```

`useQuery` used to be able to do this for you, separating this functionality out into its own hook helps make the code a little more clear and readable in my opinion.


## useMutation

The `useMutation` hook has also changed quite a bit. Gone are the `refetchQueries` and `updateQuery` options. Now, we have `onSuccess`, `onError`, and `onSettled` callbacks. These align to how people had been using the library anyhow, and are a welcome change.

If you want to run a query every time you update a variable, you can do something like this:

```jsx
const [mutatePostTodo] = useMutation(
    text =>
      fetch('/api/data', {
        method: 'POST',
        body: JSON.stringify({ text }),
      }),
    {
      // to revalidate the data and ensure the UI doesn't
      // remain in an incorrect state, ALWAYS trigger a
      // a refetch of the data, even on failure
      onSettled: () => queryCache.refetchQueries('todos'),
    }
  );

async function handleSubmit(event) {
    event.preventDefault();
    // mutate current data to optimistically update the UI
    // the fetch below could fail, so we need to revalidate
    // regardless

    queryCache.setQueryData('todos', [...data, text]);

    try {
      // send text to the API
      await mutatePostTodo(text);
      setText('');
    } catch (err) {
      console.error(err);
    }
  }
```

This not only updates the todo that you've edited but it then goes and gets the list again whether or not the POST call was successful. If you wanted to only update your list when the POST was successful, then you could swap out `onSettled` for `onSuccess`. Similarly, you could use `onError` for a failure condition. If you want to throw an exception as well when an error occurs, you can use the `throwOnError` function.

In the past, if you wanted to refetch your todos no matter, you had to do something like this:

```jsx
const [mutatePostTodo] = useMutation(
    text =>
      fetch('/api/data', {
        method: 'POST',
        body: JSON.stringify({ text }),
      }),
    {
      refetchQueries: ['todos'],
      // to revalidate the data and ensure the UI doesn't
      // remain in an incorrect state, ALWAYS trigger a
      // a refetch of the data, even on failure
      refetchQueriesOnFailure: true,
    }
  );

  async function handleSubmit(event) {
    event.preventDefault();
    // mutate current data to optimistically update the UI
    // the fetch below could fail, so we need to revalidate
    // regardless

    setQueryData('todos', [...data, text], {
      shouldRefetch: false,
    })

    try {
      // send text to the API
      await mutatePostTodo(text);
      setText('');
    } catch (err) {
      console.error(err);
    }
  }
```

While the functionality is the same, the syntax in v1 is much more declarative, and the new methods allow for much more flexibility.

## QueryCache

The previous example of how to refetch todos in v1 also illustrates how to use the new `queryCache` methods. The `queryCache` remembers all of your queries and the settings of those queries.

You can now easily `refetchQueries` by ID or `refetchQueries` by itself, which replaces `refetchAllQueries`. You can also `prefetchQuery`, `setQueryData`, `clear`, or `removeQueries`.

These also replace the standalone functions of `refetchQuery`, `refetchAllQueries`, `prefetchQuery`, `updateQuery`, and `clearQueryCache`.

## useQuery

The main hook in the library, `useQuery` has also undergone some other minor changes. Gone is the `isLoading` boolean status, instead, there is a `status` string returned with different possible values. `isFetching`, however, has remained unchanged.

Previously we could track the status like this:

```jsx
  const { data, isLoading, isFetching } = useQuery('projects', () =>
    fetch('/api/data')
  );
```

Now, we do this:

```jsx
  const { status, data, error, isFetching } = useQuery('projects', () =>
    fetch('/api/data')
  );
```

Status can either be `success`, `loading`, or `error` when returned from `useQuery` and the previously mentioned `useMutation`, but `success` can generally be assumed when we are not in a `loading` or `error` state with some JSX like this:

```jsx
    <div style={{ textAlign: 'center' }}>
      <h1>Trending Projects</h1>
      <div>
        {status === 'loading' ? (
          'Loading...'
        ) : status === 'error' ? (
          <span>Error: {error.message}</span>
        ) : (
          <>
            <div>
              {data.map(project => (
                <p key={project}>
                  <Link href="/[user]/[repo]" as={`/${project}`}>
                    <a>{project}</a>
                  </Link>
                </p>
              ))}
            </div>
          </>
        )}
      </div>
    </div>
```

Essentially, if we are not loading data, and don't have an error, we should have data to show the user.

## Keys

Keys are another concept that has changed with the v1 release of `react-query`. String only keys are still supported (they are converted to arrays under the hood), but keys with associated variables are not limited just a tuple format anymore. Instead, keys can be any valid object syntax, making them much more flexible.

The format is not the only thing that has changed, however. Variables can now be passed down to the query function, not just the key like so:

```jsx
const { status, data, error } = useQuery(
    // These will be used as the query key
    ['todo', todoId],
    // These will get passed directly to our query function
    [
      debug,
      {
        foo: true,
        bar: false,
      },
    ],
    fetchTodoById
  )
};
```

This will cause the query to be rerun if `foo` or `bar` change, but we can stop this if we want by setting a `ReactQueryConfigProvider` around the components that house this query with a queryFnParamsFilter to only pass in the key if we want.

---

Overall, while v1 will require you to update many of the places in your code where you are making API calls if you are already using `react-query`, it will create much more readable code thanks to the more declarative API available in the newest, and first, major version.