Administrando el estado con React Query. 〽️
Tags: javascript, react, spanish, tutorial
Published: 2023-03-17 15:17:06

**React Query** es una librería grande y completa que facilita el trabajo a la hora de realizar peticiones del lado del cliente hacia el servidor e incluso realiza mucho mas que eso.

Pero **¿Sabias que puedes usar esta librería como administrador de estado?**, posiblemente una alternativa a redux-toolkit, zustand, entre otros. En este articulo te mostrare como implementarlo de esta manera.

>**🚨 Nota**: para entender este articulo debes de tener conocimiento básico de como se usa React Query y también algo de conocimiento básico con TypeScript.
>

&nbsp;

## Tabla de contenido.
> 📌 [Tecnologías a utilizar.](#1)  
> 📌 [Creando el proyecto.](#2)  
> 📌 [Primeros pasos.](#3)  
> 📌 [Creando las paginas.](#4)  
> 📌 [Configurando React Query.](#5)  
> 📌 [Usando React Query como administrador de estado.](#6)  
>> 📌 [Creando las funciones para hacer las peticiones.](#7)  
>> 📌 [Obteniendo los datos con React Query.](#8)  
>> 📌 [Agregando nueva data a nuestro estado.](#9)  
>> 📌 [Eliminando los datos del estado.](#10)  
>> 📌 [Actualizando los datos del estado.](#11)  
>
> 📌 [Conclusión.](#12)  
>> 📌 [Demostración.](#13)  
>> 📌 [Código fuente.](#14)  

&nbsp;


## 📢 Tecnologías a utilizar. <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 (Los estilos los encuentras en el repositorio al final de este post)


## 📢 Creando el proyecto. <a id="2"></a>

Al proyecto le colocaremos el nombre de: **`state-management-rq`** (opcional, tu le puedes poner el nombre que gustes).
```bash
npm create vite@latest
```
Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.  
Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

```bash
cd state-management-rq
```
Luego instalamos las dependencias.
```bash
npm install
```
Después abrimos el proyecto en un editor de código (en mi caso VS code).
```bash
code .
```

## 📢 Primeros pasos. <a id="3"></a>

Primero vamos a instalar react router dom para poder crear un par de paginas en nuestra app.
```bash
npm install react-router-dom
```
Entonces, vamos a crear una carpeta **src/layout** para crear un menu de navegación muy sencillo que estará en todas las paginas.
Dentro de **src/layout** creamos el archivo **index.tsx** y agregamos lo siguiente:
```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>
        </>
    )
}
```

Luego en el archivo **src/App.tsx** vamos a borrar todo. Y vamos a crear nuestras rutas básicas.

> **Nota**: Vamos a establecer las rutas mediante mediante **createBrowserRouter**, pero si deseas puedes usar los componentes que aun dispone react-router-dom como **`<BrowserRouter/>`**, **`<Routes/>`**, **`<Route/>`**, etc. en vez de **createBrowserRouter**
>

Mediante **createBrowserRouter** vamos crear un objeto donde iremos agregando nuestras rutas. Nota que solo tengo un ruta padre, y lo que muestro es el menu de navegación, y esta ruta tiene 3 rutas hijas, que por el momento no se han creado sus paginas.

Finalmente creamos el componente App que exportamos por defecto, este componente va a renderizar un componente de react-router-dom que es el **`<RouterProvider/>`** que recibe el router que acabamos de crear.

Y con esto ya podemos navegar entre las diferentes rutas.

```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
```

Luego volveremos a este archivo para agregar mas cosas 👀.

## 📢 Creando las paginas. <a id="4"></a>
Ahora vamos a crear las tres paginas para las rutas que definimos con anterioridad.
Creamos una carpeta nueva **src/pages** y dentro creamos 3 archivos.

1. **home.tsx**

En este archivo solo vamos a listar los datos que vendrán de la API, asi que por el momento solo pondremos lo siguiente:

```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**.

Esta pagina es solo para crear nuevos usuarios o sea nueva data. Por lo cual crearemos un formulario. En esta ocasión no voy a usar un estado para controlar el input del formulario, sino simplemente usare el evento que emite el formulario cuando ejecute el **onSubmit** del mismo (Es importante ponerle el atributo **name** al 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**

En esta pagina se editara el usuario seleccionado, obtendremos su ID mediante los parámetros de la URL, tal como lo establecimos cuando creamos el 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>
        </>
    )
}
```

Ahora necesitamos colocar estas paginas en el 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
```


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

Primero instalaremos la librería.

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

Luego configuramos el proveedor en el archivo **src/App.tsx**

1. Primero crearemos el **queryClient**.

Para esta ocasión vamos a dejar estas opciones, que nos ayudaran a usar React Query también como un administrador de estado:

- `refetchOnWindowFocus`: Cuando sales de tu app y luego vuelves React Query vuelve hacer la petición de la data.
- `refetchOnMount`: Cuando el componente se vuelve a montar entonces volverá a hacer la petición.
- `retry`: Número de veces que se volverá a intentar la petición.

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

2. Luego necesitamos importar el proveedor que nos ofrece React Query y mandarle el queryClient que acabamos de crear.

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

3. Y finalmente, aunque es opcional, pero la verdad es muy muy util, instalaremos las devtools de React Query, que ayudaran mucho.
```bash
npm install @tanstack/react-query-devtools
```

Ahora colocamos las devtools dentro del proveedor de React Query.


4. El archivo quedaría asi finalmente.

```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
```




## 📢 Usando React Query como administrador de estado. <a id="6"></a>

Primero crearemos las **queryFn** que ejecutaremos.

### 📢 Creando las funciones para hacer las peticiones. <a id="7"></a>

Vamos a crear una carpeta **src/api**, y crearemos el archivo **user.ts**, aquí tendremos las funciones para hacer las peticiones, a la API.
Para no demorar mas tiempo en crear una API, usaremos [**JSON place holder**](https://jsonplaceholder.typicode.com/) ya que nos permitirá hacer un "CRUD" y no solo peticiones GET.

**Crearemos 4 funciones para hacer el CRUD.**

Primero establecemos las constantes y la interfaz

La interfaz, es la siguiente:
```tsx
export interface User {
    id: number;
    name: string;
}
```

Y las constantes son:

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

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

1. Primero haremos una función para pedir los usuarios. esta función debe retornar una promesa.
```tsx
export const getUsers = async (): Promise<User[]> => {
    return await (await fetch(URL_BASE)).json()
}
```

2. Luego la función para crear un nuevo usuario, que recibe un usuario y retorna una promesa que resuelve el nuevo usuario.
```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. Otra función para editar un usuario, que recibe el usuario a editar y retorna una promesa que resuelve el usuario editado.
```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. Finalmente, una función para eliminar el usuario, que recibe un id. Y como al eliminar un registro de la API, este no regresa nada, entonces regresaremos una promesa que resuelva el **id** para identificar que usuario fue eliminado. 
```tsx
export const deleteUser = async (id: number): Promise<number> => {
    const method = 'DELETE'
    await fetch(`${URL_BASE}/${id}`, { method })
    return id
}
```

Asi quedaría este archivo:

```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
}
```


### 📢 Obteniendo los datos con React Query. <a id="8"></a>

En vez de colocar el código de React Query directamente en el componente, los colocaremos de una vez en un **custom hook** para tener centralizado nuestro código en un solo lugar.

Asi que crearemos una carpeta **src/hook** y dentro un archivo llamado **useUser.tsx**

El primer custom hook que crearemos sera el de **useGetUsers** el cual solo retorna las propiedades que regresa el hook de **useQuery**. 

> Cabe notar que **useQuery**, necesita de 2 parámetros, un arreglo de strings para identificar la query, y el segundo parámetro la función que hemos realizado anteriormente que es para obtener los usuarios de la 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>
        </>
    )
}
```

Hasta aquí, solo hemos establecido la data y guardando los estos datos en la cache (que actuara como nuestra store que almacena el estado), aun no hemos usado/modificado el estado de este componente en algún otro lugar.

### 📢 Agregando nueva data a nuestro estado. <a id="9"></a>

Vamos a **src/hooks/useUser.tsx** y crearemos un nuevo custom hook para crear nuevos usuarios.

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

En esta ocasión y en las que siguen, usaremos **useMutation** ya que vamos a ejecutar una petición de tipo POST, para crear un nuevo registro. 

useMutation recibe la queryFn a ejecutar, en este caso le pasaremos la función que creamos para agregar un nuevo usuario.


```tsx
export const useCreateUser = () => {
    return useMutation(createUser)
}
```
Le pasaremos un segundo parámetro que sera un objeto, el cual accederemos a la propiedad **onSuccess** que es una función que se ejecuta cuando la petición ha resultado exitosa.

onSuccess recibe varios parámetros, y usaremos el primero que es la data que regresa la función **createUser** que en este caso debe ser el usuario nuevo.

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

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

Ahora lo que queremos es acceder a la cache (o sea nuestro estado) y agregar este nuevo usuario creado.

Para esta tarea usaremos otro hook de React Query, **useQueryClient**

> Nota: No desestructures ninguna propiedad del hook useQueryClient porque perderás la referencia y dicha propiedad no funcionara como tu quieres.
>

Ahora, dentro del cuerpo de la función de **onSuccess**, vamos a establecer el nuevo dato, mediante la propiedad **setQueryData**.

**setQueryData**, necesita 2 parámetros, el primero es la queryKey para identificar que parte de la cache vas a obtener los datos y modificarlos.


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

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

El segundo parámetro es la función para establecer la nueva data. La cual debe recibir por parámetro, la data que ya esta en la cache, que en este caso puede ser un arreglo de usuarios o undefined.

Lo que se hará, sera una validación,  donde si ya existen usuarios en la cache, solamente agregamos el nuevo usuario y esparcimos los usuarios anteriores, de lo contrario solo retornamos el usuario creado en un arreglo. 

```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])
        }
    })
}
```

>Nota:  Observa la linea comentada en el código anterior
>```tsx
> queryClient.invalidateQueries([key])
>```
> **Esta linea de código, sirve para invalidar la cache y volver a pedir los datos al servidor.  Es lo que normalmente quieres hacer cuando haces algún tipo de petición POST, PUT, DELETE, etc**.
>
> En mi caso, comento esta linea, porque la API JSON placeholder no modifica los datos, solo simula hacerlo. Por lo que si yo hago una petición DELETE para eliminar un registro y todo sale bien y luego coloco **invalidateQueries** pues me regresara de nuevo todos los usuarios y parecerá que no hubo algún cambio en la data.


Una vez aclarado esto, usaremos el custom hook en **src/pages/createUser.tsx**

Establecemos el custom hook, en este caso, puedes desestructurar las props que retorna este hook pero yo no lo hare solo por gusto. (Aunque cuando usemos mas de un hook, esta sintaxis sera una buena opción para evitar conflicto con los nombres de las props)
```tsx
    const create_user = useCreateUser()
```

Ahora en el handleSubmit, accederemos a la propiedad mutateAsync, y gracias a TypeScript sabemos que argumentos debemos pasar, el cual es el nombre del nuevo usuario. 

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

> Si te preguntas de donde sacamos este argumento, pues es de la función, pues es de la función de crearUsuario del archivo **src/>api/user.ts**, depende de lo que reciba como parámetro, es lo que enviaremos como argumento.
>

Y la pagina quedaría de este manera:

```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>
    )
}
```


### 📢 Eliminando los datos del estado. <a id="10"></a>

Ahora toca eliminar datos, y los paso son similares que cuando creamos datos.

- Creamos el custom hook,**useDeleteUser**.
- Usamos **useMutation**, mandando la función a ejecutar, **deleteUser**.
- Accedemos a la propiedad onSuccess, para ejecutar la función 
- Usamos useQueryClient para modificar la data en la cache, una vez que sea exitosa la petición.
- Mandamos la queryKey a la propiedad **setQueryData**,  y el función, validamos si existen datos, en el caso de que si, filtramos la data por el ID que recibimos del onSuccess y excluimos al usuario que acabamos de eliminar, regresando el nuevo arreglo sin el usuario eliminado. 

```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])
            )
        }
    });
}
```
Usamos nuestro custom hook en **src/pages/editUser.tsx**

Pero antes vamos a separar en componentes diferentes las acciones a realizar. Primero crearemos un componente en el mismo archivo, lo nombraremos **DeleteUser** el cual recibe el id del usuario a eliminar.
```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 tendrá lo siguiente.

Establecemos el custom hook **useDeleteUser** y accedemos al método mutateAsync para realizar ejecutar la petición y le mandamos el 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>}
        </>
    )
}
```

Y listo, una vez eliminado, regresas a la pagina Home y notaras que se ha eliminado correctamente el usuario. **Claro que si refrescas en navegador, este usuario vuelve aparecer porque estamos usando JSON placeholder**.

### 📢 Actualizando los datos del estado. <a id="11"></a>

Ahora toca actualizar un usuario siguiendo los mismos pasos.

- Creamos el custom hook,**useEditUser**.
- Usamos **useMutation**, mandando la función a ejecutar, **editUser**.
- Accedemos a la propiedad onSuccess, para ejecutar la función 
- Usamos useQueryClient para modificar la data en la cache, una vez que sea exitosa la petición.
- Mandamos la queryKey a la propiedad **setQueryData**,  y el función, validamos si existen datos, en el caso de que si, identificamos el usuario que se modifico mediante el ID y le asignamos su nuevo valor ya modificado.

```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
                }
            )
        }
    })
}
```

Ahora vamos a **src/pages/editUser.tsx**  y crearemos 2 componentes mas para mostrarte un inconveniente.
Creamos los componentes **ViewUser** para ver el usuario y **EditUser** que sera un formulario para editar el usuario.

```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** recibe el id, y hace uso del useGetUsers para traer todos los usuarios (lo cual no dispara otra petición, sino que accede a los que están en la cache). 
Filtramos el usuario y lo mostramos en pantalla.

```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**, también recibe un ID. De hecho este componente es bastante igual al de la pagina **createUser.tsx**, que incluso pueden reutilizarlo, pero en mi caso no lo hare.

Pasamos a usar el custom hook **useEditUser**, accedemos a su método **mutateAsync** y le pasamos los argumentos necesarios. Y listo ya podrás editar el usuario seleccionado. 

```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>
        </>
    )
}
```

**Pero cuidado**, notaras que cuando un usuario se actualiza correctamente, el componente **ViewUser** no se renderiza, o sea mantiene el valor del nombre del usuario anterior. Pero si regresas a la pagina Home, notaras que el nombre del usuario si se actualizo.

Esto es debido a que se necesita un nuevo renderizado para que cambien el componente **ViewUser**. 

Para ello se me ocurre una solución. Crear un nuevo custom hook que maneje un observable y estar pendiente de los cambios en cierta parte de la cache.

En este custom hook vamos a usar el otros custom hook useGetUsers y el hook useQueryClient. 

1. Primero usamos el useGetUsers y retornamos sus props, pero sobrescribimos la prop data, ya que es la que tenemos que estar al pendiente de cambios.


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

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

2. Creamos un estado para manejar el arreglo de usuarios, y ese estado se lo asignamos a la prop data.

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

    const get_users = useGetUsers()

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

    return {
        ...get_users,
        data: users,
    }
}
```
3. Inicializamos el estado con la data existente en la cache, en caso de que no haya datos en la cache retornamos un arreglo vació. Esto lo logramos usando el hook **useQueryClient** y su propiedad **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. Ahora si usaremos un efecto para manejar el observador. Dentro creamos una nueva instancia de QueryObserver que requiere dos argumentos, el **queryClient** y un objeto donde necesita la **queyKey** para saber que parte de la cache se estará observando.

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

}, [])
```

5. Ahora necesitamos suscribirnos al observador, por lo que ejecutamos la propiedad **subscribe** del observer. El **subscribe** recibe un callback el cual devuelve un objeto que es básicamente las mismas propiedades que devuelve un hook como **useQuery** por lo que validamos si en la propiedad data existe data, entonces actualizamos el estado con esta nueva data.

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

}, [])
```

6. Recuerda que una buena practica es cancelar la suscripción cuando el componente se desmonte.

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

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

Y asi quedaría este nuevo custom hook.

```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,
    }
}
```

Ahora solo es cuestión de usarlo en el componente donde queremos estar al pendiente de estos datos. Como en el componente **ViewUser**.

> No olvides importar 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>
        </>
    )
}
```

Ahora si cuando intentes actualizar los datos o eliminarlo, veras como el componente **ViewUser** también se actualizara una vez la petición sea realizada de manera exitosa.

Y con esto terminaríamos el CRUD usando como gestor de estado la cache de React Query.


## 📢 Conclusión. <a id="12"></a>

React Query es una librería muy potente que sin duda nos ayuda con el manejo de las peticiones. Pero ahora puedes extenderte mucho mas sabiendo que puedes utilizarlo como un gestor de estado, probablemente una alternativa más.

Espero que te haya gustado esta publicación y que también espero haberte ayudado a extender mas tus conocimientos con React Query.

Si conoces alguna otra forma distinta o mejor de como gestionar el estado usando React Query, con gusto puedes hacerla saber en los comentarios.

> **Te invito a que revises mi portafolio en caso de que estés interesado en contactarme para algún proyecto! [Franklin Martinez Lucas](https://franklin-dev.netlify.app/)**

> 🔵 No olvides seguirme también en twitter: [**@Frankomtz361**](https://twitter.com/Frankomtz361)

### 📢 Demostración simple. <a id="13"></a>

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


### 📢 Código fuente. <a id="14"></a>
[https://github.com/Franklin361/state-management-react-query](https://github.com/Franklin361/state-management-react-query)