Managing state with React Query. 〽️
Tags: javascript, react, tutorial, webdev
Published: 2023-03-17 15:17:00

**React Query** is a large and complete library that facilitates the work when making client-side requests to the server and even performs much more than that.

But **Did you know that you can use this library as a state manager?**, possibly an alternative to redux-toolkit, zustand, among others. In this article I will show you how to implement it this way.

>**🚨 Note**: to understand this article you should have basic knowledge of how to use React Query and also some basic knowledge with TypeScript.
>

&nbsp;

## Table of contents.
> 📌 [Technologies to be used.](#1)  
> 📌 [Creating the project.](#2)  
> 📌 [First steps.](#3)  
> 📌 [Creating the pages.](#4)  
> 📌 [Configuring React Query.](#5)  
> 📌 [Using React Query as status manager.](#6)  
>> 📌 [Creating the functions to make the requests.](#7)  
>> 📌 [Getting the data with React Query.](#8)  
>> 📌 [Adding new data to our state.](#9)  
>> 📌 [Removing data from the state.](#10)  
>> 📌 [Updating the status data.](#11)  
>
> 📌 [Conclusion.](#12)  
>> 📌 [Demonstration.](#13)  
>> 📌 [Source code.](#14)  
&nbsp;


## 📢 Technologies to be used. <a id="1"></a>

- React JS **18.2.0**
- React Query **4.20.4**
- React Router Dom **6.6.1**
- TypeScript **4.9.3**
- Vite JS **4.0.0**
- CSS vanilla (You can find the styles in the repository at the end of this post) 


## 📢 Creating the project. <a id="2"></a>

We will name the project: **`state-management-rq`** (optional, you can name it whatever you like).
```bash
npm create vite@latest
```
We create the project with Vite JS and select React with TypeScript.  
Then we run the following command to navigate to the directory just created.

```bash
cd state-management-rq
```
Then we install the dependencies.
```bash
npm install
```
Then we open the project in a code editor (in my case VS code).
```bash
code .
```


## 📢 First steps. <a id="3"></a>

First we are going to install a dom router to be able to create a couple of pages in our app.
```bash
npm install react-router-dom
```
So, let's create a **src/layout** folder to create a very simple navigation menu that will be on all the pages.
Inside **src/layout** we create the **index.tsx** file and add the following:
```tsx
import { NavLink, Outlet } from 'react-router-dom'

type LinkActive = { isActive: boolean }

const isActiveLink = ({ isActive }: LinkActive) => `link ${isActive ? 'active' : ''}`

export const Layout = () => {
    return (
        <>
            <nav>
                <NavLink className={isActiveLink} to="/">Home 🏠</NavLink>
                <NavLink className={isActiveLink} to="/create">Create ✍️</NavLink>
            </nav>

            <hr className='divider' />

            <div className='container'>
                <Outlet />
            </div>
        </>
    )
}
```

Then in the **src/App.tsx** file we are going to delete everything. And we are going to create our basic routes.

> **Note**: We are going to set the routes using **createBrowserRouter**, but if you want you can use the components that react-router-dom still has like **`<BrowserRouter/>`**, **`<Routes/>`**, **`<Route/>`**, etc. instead of **createBrowserRouter**.
>

By **createBrowserRouter** we are going to create an object where we will add our routes. Note that I only have a parent route, and what I show is the navigation menu, and this route has 3 daughter routes, which for the moment have not been created their pages.

Finally we create the component App that we export by default, this component is going to render a component of react-router-dom that is the **`<RouterProvider/>`** that receives the router that we have just created.

And with this we can navigate between the different routes.

```tsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Layout } from './layout';
import { Home } from './pages/home';

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        index: true,
        element: <>create</>,
      },
      {
        path: "/create",
        element: <>create</>,
      },
      {
        path: "/:id",
        element: <>edit</>,
      },
    ]
  }
]);

const App = () => ( <RouterProvider router={router} /> )

export default App
```

Then we will come back to this file to add more stuff 👀.


## 📢 Creating the pages. <a id="4"></a>
Now we are going to create the three pages for the paths we defined earlier.
Create a new folder **src/pages** and inside create 3 files.

1. **home.tsx**

In this file we are only going to list the data that will come from the API, so for the moment we will only put the following:

```tsx
import { Link } from 'react-router-dom'

export const Home = () => {
    return (
        <>
            <h1>Home</h1>

            <div className="grid">
                <Link to={`/1`} className='user'>
                    <span>username</span>
                </Link>
            </div>
        </>
    )
}
```

2. **createUser.tsx**.

This page is only for creating new users or new data. So we will create a form. In this occasion I am not going to use a state to control the input of the form, but simply I will use the event that emits the form when it executes the **onSubmit** of the same one (It is important to put the attribute **name** to the input).

```tsx
export const CreateUser = () => {

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        const form = e.target as HTMLFormElement
        const data = Object.fromEntries(new FormData(form))
        
        // TODO: create new user

        form.reset()
    }

    return (
        <div>
            <h1>Create User</h1>
            <form onSubmit={handleSubmit} className='mt'>
                <input name='user' type="text" placeholder='Add new user' />
                
                <button>Add User</button>
            </form>
        </div>
    )
}
```

3. **editUser.tsx**

In this page the selected user will be edited, we will obtain his ID by means of the parameters of the URL, as we established it when we created the router.


```tsx
import { useParams } from 'react-router-dom';

export const EditUser = () => {
    const params = useParams()

    const { id } = params

    if (!id) return null

    return (
        <>
            <span>Edit user {id}</span>
        </>
    )
}
```

Now we need to place these pages in the router!

```tsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Layout } from './layout';
import { CreateUser } from './pages/createUser';
import { EditUser } from './pages/editUser';
import { Home } from './pages/home';

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "/create",
        element: <CreateUser />,
      },
      {
        path: "/:id",
        element: <EditUser />,
      },
    ]
  }
]);

const App = () => (
    <RouterProvider router={router} />
)

export default App
```


## 📢 Configuring React Query. <a id="5"></a>

First we will install the library.

```bash
npm install @tanstack/react-query
```

Then we configure the provider in the **src/App.tsx** file.

1. First we will create the **queryClient**.

For this occasion we are going to leave these options, that will help us to use React Query also as a state manager:

- `refetchOnWindowFocus`: When you exit your app and then come back React Query returns to make the request for the data.
- `refetchOnMount`: When the component is remounted then it will make the request again.
- `retry`: Number of times to retry the request.

```tsx
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      refetchOnMount: false,
      retry: 1,
    },
  },
});
```

2. Then we need to import the provider that offers us React Query and send it the queryClient we just created.

```tsx
const App = () => (
  <QueryClientProvider client={queryClient}>
    <RouterProvider router={router} />
  </QueryClientProvider>
)
```

3. And finally, although it is optional, but it is very very useful, we will install the React Query devtools, which will help a lot.
```bash
npm install @tanstack/react-query-devtools
```

Now we place the devtools inside the React Query provider.

4. The file would finally look like this.

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Layout } from './layout';
import { CreateUser } from './pages/createUser';
import { EditUser } from './pages/editUser';
import { Home } from './pages/home';

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "/create",
        element: <CreateUser />,
      },
      {
        path: "/:id",
        element: <EditUser />,
      },
    ]
  }
]);

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnMount: false,
      refetchOnWindowFocus: false,
      retry: 1
    },
  },
});

const App = () => (
  <QueryClientProvider client={queryClient}>
    <ReactQueryDevtools initialIsOpen={false} />
    <RouterProvider router={router} />
  </QueryClientProvider>
)

export default App
```


## 📢 Using React Query as status manager. <a id="6"></a>

First we will create the **queryFn** that we will execute.

### 📢 Creating the functions to make the requests. <a id="7"></a>

We are going to create a folder **src/api**, and we will create the file **user.ts**, here we will have the functions to make the requests, to the API.
In order not to take more time to create an API, we will use [**JSON place holder**](https://jsonplaceholder.typicode.com/) because it will allow us to make a "CRUD" and not only GET requests.

**We will create 4 functions to do the CRUD.

First we set the constants and the interface

The interface is as follows:
```tsx
export interface User {
    id: number;
    name: string;
}
```

And the constants are:

```tsx
import { User } from '../interface';

const URL_BASE = 'https://jsonplaceholder.typicode.com/users'
const headers = { 'Content-type': 'application/json' }
```

1. First we will make a function to request the users. this function must return a promise.
```tsx
export const getUsers = async (): Promise<User[]> => {
    return await (await fetch(URL_BASE)).json()
}
```

2. Then the function to create a new user, which receives a user and returns a promise that resolves the new user.
```tsx
export const createUser = async (user: Omit<User, 'id'>): Promise<User> => {
    const body = JSON.stringify(user)
    const method = 'POST'
    return await (await fetch(URL_BASE, { body, method, headers })).json()
}
```

3. Another function to edit a user, which receives the user to edit and returns a promise that resolves the edited user.
```tsx
export const editUser = async (user: User): Promise<User> => {
    const body = JSON.stringify(user)
    const method = 'PUT'
    return await (await fetch(`${URL_BASE}/${user.id}`, { body, method, headers })).json()
}
```

4. Finally, a function to delete the user, which receives an id. And since when deleting a record from the API, it does not return anything, then we will return a promise that resolves the **id** to identify which user was deleted. 
```tsx
export const deleteUser = async (id: number): Promise<number> => {
    const method = 'DELETE'
    await fetch(`${URL_BASE}/${id}`, { method })
    return id
}
```

This is how this file would look like:

```tsx
import { User } from '../interface';

const URL_BASE = 'https://jsonplaceholder.typicode.com/users'
const headers = { 'Content-type': 'application/json' }

export const getUsers = async (): Promise<User[]> => {
    return await (await fetch(URL_BASE)).json()
}

export const createUser = async (user: Omit<User, 'id'>): Promise<User> => {
    const body = JSON.stringify(user)
    const method = 'POST'
    return await (await fetch(URL_BASE, { body, method, headers })).json()
}

export const editUser = async (user: User): Promise<User> => {
    const body = JSON.stringify(user)
    const method = 'PUT'
    return await (await fetch(`${URL_BASE}/${user.id}`, { body, method, headers })).json()
}

export const deleteUser = async (id: number): Promise<number> => {
    const method = 'DELETE'
    await fetch(`${URL_BASE}/${id}`, { method })
    return id
}
```


### 📢 Getting the data with React Query. <a id="8"></a>

Instead of placing the React Query code directly in the component, we will place them all at once in a **custom hook** to have our code centralized in one place.

So we will create a folder **src/hook** and inside a file called **useUser.tsx**.

The first custom hook we will create will be **useGetUsers** which only returns the properties returned by the **useQuery** hook. 

> Note that **useQuery**, needs 2 parameters, an array of strings to identify the query, and the second parameter is the function that we have done previously which is to get the users from the API.
>


```tsx
import { useQuery } from '@tanstack/react-query';
import { getUsers } from '../api/user';

const key = 'users'

export const useGetUsers = () => {
    return useQuery([key], getUsers);
}
```

Ahora, toca usar **useGetUsers**. Como notaras, es lo mismo que si usamos useQuery, pero sin necesitar establecer la **queryKey** y la **queryFn**, asiéndolo mas fácil de leer


```tsx
import { Link } from 'react-router-dom'
import { useGetUsers } from '../hook/useUser'

export const Home = () => {

    const { data, isLoading, isError } = useGetUsers()

    return (
        <>
            <h1>Home</h1>

            {isLoading && <span>fetching a character...</span>}
            {isError && <span>Ups! it was an error 🚨</span>}

            <div className="grid">
                {
                    data?.map(user => (
                        <Link to={`/${user.id}`} key={user.id} className='user'>
                            <span>{user.name}</span>
                        </Link>
                    ))
                }
            </div>
        </>
    )
}
```

So far, we have only set the data and stored this data in the cache (which will act as our store that stores the state), we have not yet used/modified the state of this component elsewhere.

### 📢 Adding new data to our state. <a id="9"></a>

Let's go to **src/hooks/useUser.tsx** and create a new custom hook to create new users.

```tsx
export const useCreateUser = () => {}
```

In this occasion and in the following ones, we will use **useMutation** because we are going to execute a POST request to create a new record. 

useMutation receives the queryFn to execute, in this case we will pass it the function we created to add a new user.


```tsx
export const useCreateUser = () => {
    return useMutation(createUser)
}
```
We will pass a second parameter which will be an object, which will access the **onSuccess** property which is a function that is executed when the request is successful.

onSuccess receives several parameters, and we will use the first one which is the data returned by the **createUser** function which in this case must be the new user.

```tsx
export const useCreateUser = () => {

return useMutation(createUser, {
        onSuccess: (user: User) => {}
    })
}
```

Now what we want to do is to access the cache (our state) and add this newly created user.

For this task we will use another React Query hook, **useQueryClient**.

> **🚨 Note**: Do not destructure any property of the hook useQueryClient because you will lose the reference and this property will not work as you want.
>

Now, inside the body of the **onSuccess** function, let's set the new data, using the **setQueryData** property.

**setQueryData**, needs 2 parameters, the first one is the queryKey to identify which part of the cache you are going to get the data and modify it.


```tsx
export const useCreateUser = () => {
    const queryClient = useQueryClient();

    return useMutation(createUser, {
        onSuccess: (user: User) => {
            queryClient.setQueryData([key])
        }
    })
}
```

The second parameter is the function to set the new data. Which must receive by parameter, the data that is already in the cache, which in this case can be an array of users or undefined.

What will be done, will be a validation, where if there are already users in the cache, we only add the new user and spread the previous users, otherwise we only return the user created in an array. 

```tsx
export const useCreateUser = () => {
    const queryClient = useQueryClient();

    return useMutation(createUser, {
        onSuccess: (user: User) => {

            queryClient.setQueryData([key],
                (prevUsers: User[] | undefined) => prevUsers ? [user, ...prevUsers] : [user]
            )

            // queryClient.invalidateQueries([key])
        }
    })
}
```

>**🚨 Note**: Observe the line commented in the previous code
>```tsx
> queryClient.invalidateQueries([key])
>```
> **This line of code is used to invalidate the cache and re-request the data from the server.  This is what you normally want to do when you make some kind of POST, PUT, DELETE, etc. request.**.
>
> In my case, I comment this line, because the JSON placeholder API does not modify the data, it only simulates it. So if I make a DELETE request to delete a record and everything goes well and then I put **invalidateQueries**, it will return all the users again and it will seem that there was no change in the data.


Once this is clear, we will use the custom hook in **src/pages/createUser.tsx**.

We set the custom hook, in this case, you can unstructure the props returned by this hook but I won't do it just for fun (although when we use more than one hook, this syntax will be a good option to avoid conflict with the names of the props).
```tsx
    const create_user = useCreateUser()
```

Now in the handleSubmit, we will access the mutateAsync property, and thanks to TypeScript we know what arguments we must pass, which is the name of the new user. 

```tsx
await create_user.mutateAsync({ name: data.user as string  })
```

> If you wonder where we get this argument, it is from the function, it is from the createUser function of the file **src/>api/user.ts**, it depends on what it receives as parameter, it is what we will send as argument.
>

And the page would look like this:

```tsx
import { useCreateUser } from '../hook/useUser'

export const CreateUser = () => {

    const create_user = useCreateUser()

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        const form = e.target as HTMLFormElement
        const data = Object.fromEntries(new FormData(form))
        
        await create_user.mutateAsync({ name: data.user as string })

        form.reset()
    }

    return (
        <div>
            <h1>Create User</h1>
            <form onSubmit={handleSubmit} className='mt'>
                <input name='user' type="text" placeholder='Add new user' />
                {create_user.isLoading && <span>creating user...</span>}
                <button>Add User</button>
                {create_user.isSuccess && <span>User created successfully ✅</span>}
                {create_user.isError && <span>Ups! it was an error 🚨</span>}
            </form>
        </div>
    )
}
```


### 📢 Removing data from the state. <a id="10"></a>

Now it is time to delete data, and the steps are similar to when we create data.

- We create the custom hook, **useDeleteUser**.
- We use **useMutation**, sending the function to execute, **deleteUser**.
- We access to the onSuccess property, to execute the function. 
- We use use useQueryClient to modify the data in the cache, once the request is successful.
- We send the queryKey to the **setQueryData** property, and the function, we validate if there is data, if yes, we filter the data by the ID we received from the onSuccess and exclude the user we just deleted, returning the new array without the deleted user. 

```tsx
export const useDeleteUser = () => {

    const queryClient = useQueryClient();

    return useMutation(deleteUser, {
        onSuccess: (id) => {
            queryClient.setQueryData([key],
                (prevUsers: User[] | undefined) => prevUsers ? prevUsers.filter(user => user.id !== id) : prevUsers
                // queryClient.invalidateQueries([key])
            )
        }
    });
}
```
We use our custom hook in **src/pages/editUser.tsx**.

But before we are going to separate in different components the actions to be performed. First we will create a component in the same file, we will name it **DeleteUser** which receives the id of the user to delete.
```tsx
import { useParams } from 'react-router-dom';
import { useDeleteUser } from '../hook/useUser';
import { User } from '../interface';

export const EditUser = () => {
    const params = useParams()

    const { id } = params

    if (!id) return null

    return (
        <>
            <DeleteUser id={+id} />
        </>
    )
}
```

**DeleteUser** will have the following.

We set the custom hook **useDeleteUser** and access the mutateAsync method to execute the request and send it the id.

```tsx
export const DeleteUser = ({ id }: Pick<User, 'id'>) => {
    const delete_user = useDeleteUser()

    const onDelete = async () => {
        await delete_user.mutateAsync(id)
    }

    return (
        <>
            {delete_user.isLoading && <span>deleting user...</span>}

            <button onClick={onDelete}>Delete User</button>

            {delete_user.isSuccess && <span>User deleted successfully ✅, go back home</span>}
            {delete_user.isError && <span>Ups! it was an error 🚨</span>}
        </>
    )
}
```

And that's it, once removed, go back to the Home page and you will notice that the user has been removed correctly. **Of course, if you refresh the browser, this user reappears because we are using JSON placeholder.**

### 📢 Updating the status data. <a id="11"></a>

Now it is time to update a user following the same steps.

- We create the custom hook, **useEditUser**.
- We use **useMutation**, sending the function to execute, **editUser**.
- We access to the onSuccess property, to execute the function. 
- We use use useQueryClient to modify the data in the cache, once the request is successful.
- We send the queryKey to the **setQueryData** property, and the function, we validate if data exists, if yes, we identify the user that was modified through the ID and we assign its new value already modified.

```tsx
export const useEditUser = () => {
    const queryClient = useQueryClient();

    return useMutation(editUser, {
        onSuccess: (user_updated: User) => {

            queryClient.setQueryData([key],
                (prevUsers: User[] | undefined) => {
                    if (prevUsers) {
                        prevUsers.map(user => {
                            if (user.id === user_updated.id) {
                                user.name = user_updated.name
                            }
                            return user
                        })
                    }
                    return prevUsers
                }
            )
        }
    })
}
```

Now let's go to **src/pages/editUser.tsx** and create 2 more components to show you a drawback.
We create the components **ViewUser** to see the user and **EditUser** which will be a form to edit the user.

```tsx
import { useParams } from 'react-router-dom';
import { useDeleteUser, useEditUser, useGetUsers } from '../hook/useUser';
import { User } from '../interface';

export const EditUser = () => {
    const params = useParams()

    const { id } = params

    if (!id) return null

    return (
        <>
            <ViewUser id={+id} />
            <EditUserForm id={+id} />
            <DeleteUser id={+id} />
        </>
    )
}
```

**ViewUser** receives the id, and makes use of useGetUsers to fetch all users (which does not trigger another request, but accesses those in the cache). 
We filter the user and display it on screen.

```tsx
export const ViewUser = ({ id }: Pick<User, 'id'>) => {

    const get_users = useGetUsers()

    const user_selected = get_users.data?.find(user => user.id === +id)

    if (!user_selected) return null

    return (
        <>
            <h1>Edit user: {id}</h1>
            <span>User name: <b>{user_selected?.name}</b></span>
        </>
    )
}
```

**EditUser**, it also receives an ID. In fact this component is quite the same as the one in the **createUser.tsx** page, you can even reuse it, but in my case I won't do it.

We use the custom hook **useEditUser**, we access to its method **mutateAsync** and we pass the necessary arguments. And ready you will be able to edit the selected user. 

```tsx

export const EditUserForm = ({ id }: Pick<User, 'id'>) => {

    const edit_user = useEditUser()

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        const form = e.target as HTMLFormElement
        const data = Object.fromEntries(new FormData(form))
        await edit_user.mutateAsync({ name: data.user as string, id })
        form.reset()
    }

    return (
        <>
            <form onSubmit={handleSubmit}>
                <input name='user' type="text" placeholder='Update this user' />
                {edit_user.isLoading && <span>updating user...</span>}
                <button>Update User</button>
                {edit_user.isSuccess && <span>User updated successfully ✅</span>}
                {edit_user.isError && <span>Ups! it was an error 🚨</span>}
            </form>
        </>
    )
}
```

**But be careful**, you will notice that when a user is updated correctly, the **ViewUser** component is not rendered, that is, it keeps the value of the previous user's name. But if you go back to the Home page, you will notice that the user's name is updated.

This is because a new rendering is needed to change the **ViewUser** component. 

For this I can think of a solution. Create a new custom hook that handles an observable and be aware of the changes in a certain part of the cache.

In this custom hook we are going to use the other custom hook **useGetUsers** and the hook **useQueryClient**. 

1. First we use the useGetUsers and return its props, but we overwrite the prop data, since it is the one that we have to be aware of changes.


```tsx
export const useGetUsersObserver = () => {
  
    const get_users = useGetUsers()

    return {
      ...get_users,
        data:[],
    }
}
```

2. We create a state to manage the user array, and we assign that state to the prop data.

```tsx
export const useGetUsersObserver = () => {

    const get_users = useGetUsers()

    const [users, setUsers] = useState<User[]>()

    return {
        ...get_users,
        data: users,
    }
}
```
3. We initialize the state with the existing data in the cache, in case there is no data in the cache we return an empty array. This is achieved using the hook **useQueryClient** and its property **getQueryData**.

```tsx
export const useGetUsersObserver = () => {

    const get_users = useGetUsers()

    const queryClient = useQueryClient()

    const [users, setUsers] = useState<User[]>(() => {
      
        const data = queryClient.getQueryData<User[]>([key])
        return data ?? []
    })

    return {
        ...get_users,
        data: users,
    }
}
```

4. Now we will use an effect to handle the observer. Inside we create a new instance of QueryObserver that requires two arguments, the **queryClient** and an object where it needs the **queyKey** to know which part of the cache will be watched.

```tsx
useEffect(() => {
    const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })

}, [])
```

5. Now we need to subscribe to the observer, so we execute the **subscribe** property of the observer. The **subscribe** receives a callback which returns an object that is basically the same properties that returns a hook like **useQuery** so we validate if in the data property there is data, then we update the state with this new data.

```tsx
useEffect(() => {
    const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })
    
    const unsubscribe = observer.subscribe(result => {
        if (result.data) setUsers(result.data)
    })

}, [])
```

6. Remember that a good practice is to cancel the subscription when the component is disassembled.

```tsx
useEffect(() => {
    const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })
    
    const unsubscribe = observer.subscribe(result => {
        if (result.data) setUsers(result.data)
    })

    return () => {
        unsubscribe()
    }
}, [])
```

And this is how this new custom hook would look like.

```tsx

export const useGetUsersObserver = () => {

    const get_users = useGetUsers()

    const queryClient = useQueryClient()

    const [users, setUsers] = useState<User[]>(() => {

        const data = queryClient.getQueryData<User[]>([key])
        return data ?? []
    })


    useEffect(() => {
        const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })

        const unsubscribe = observer.subscribe(result => {
            if (result.data) setUsers(result.data)
        })

        return () => {
            unsubscribe()
        }
    }, [])

    return {
        ...get_users,
        data: users,
    }
}
```

Now it is only a question of using it in the component where we want to be aware of this data. As in the **ViewUser** component.

> Don't forget to import **useGetUsersObserver**.

```tsx
export const ViewUser = ({ id }: Pick<User, 'id'>) => {

    // const get_users = useGetUsers()
    const get_users = useGetUsersObserver()

    const user_selected = get_users.data?.find(user => user.id === +id)

    if (!user_selected) return null

    return (
        <>
            <h1>Edit user: {id}</h1>
            <span>User name: <b>{user_selected?.name}</b></span>
        </>
    )
}
```

Now if when you try to update the data or delete it, you will see how the **ViewUser** component will also be updated once the request is successful.

And with this we would finish the CRUD using as state manager the React Query cache.



## 📢 Conclusion. <a id="12"></a>

React Query is a very powerful library that certainly helps us with request handling. But now you can extend it much more knowing that you can use it as a status manager, probably one more alternative.

I hope you liked this post and I also hope I helped you to extend your knowledge with React Query.

If you know any other different or better way on how to manage status using React Query, feel free to let me know in the comments.

> **I invite you to check my portfolio in case you are interested in contacting me for a project!. [Franklin Martinez Lucas](https://franklin-dev.netlify.app/)**

> 🔵 Don't forget to follow me also on twitter: [**@Frankomtz361**](https://twitter.com/Frankomtz361)

### 📢 Demo. <a id="13"></a>

[https://rq-state-management.netlify.app/](https://rq-state-management.netlify.app/)

### 📢 Source code. <a id="14"></a>

[https://github.com/Franklin361/state-management-react-query](https://github.com/Franklin361/state-management-react-query)