Administrando el estado con React Query. 〽️
Franklin Martinez
Posted on March 17, 2023
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.
Tabla de contenido.
📌 Tecnologías a utilizar.
📌 Creando el proyecto.
📌 Primeros pasos.
📌 Creando las paginas.
📌 Configurando React Query.
📌 Usando React Query como administrador de estado.📌 Creando las funciones para hacer las peticiones.
📌 Obteniendo los datos con React Query.
📌 Agregando nueva data a nuestro estado.
📌 Eliminando los datos del estado.
📌 Actualizando los datos del estado.
📢 Tecnologías a utilizar.
- 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.
Al proyecto le colocaremos el nombre de: state-management-rq
(opcional, tu le puedes poner el nombre que gustes).
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.
cd state-management-rq
Luego instalamos las dependencias.
npm install
Después abrimos el proyecto en un editor de código (en mi caso VS code).
code .
📢 Primeros pasos.
Primero vamos a instalar react router dom para poder crear un par de paginas en nuestra app.
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:
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.
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.
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.
- 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:
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>
</>
)
}
- 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).
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>
)
}
- 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.
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!
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.
Primero instalaremos la librería.
npm install @tanstack/react-query
Luego configuramos el proveedor en el archivo src/App.tsx
- 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.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
refetchOnMount: false,
retry: 1,
},
},
});
- Luego necesitamos importar el proveedor que nos ofrece React Query y mandarle el queryClient que acabamos de crear.
const App = () => (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
)
- Y finalmente, aunque es opcional, pero la verdad es muy muy util, instalaremos las devtools de React Query, que ayudaran mucho.
npm install @tanstack/react-query-devtools
Ahora colocamos las devtools dentro del proveedor de React Query.
- El archivo quedaría asi finalmente.
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.
Primero crearemos las queryFn que ejecutaremos.
📢 Creando las funciones para hacer las peticiones.
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 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:
export interface User {
id: number;
name: string;
}
Y las constantes son:
import { User } from '../interface';
const URL_BASE = 'https://jsonplaceholder.typicode.com/users'
const headers = { 'Content-type': 'application/json' }
- Primero haremos una función para pedir los usuarios. esta función debe retornar una promesa.
export const getUsers = async (): Promise<User[]> => {
return await (await fetch(URL_BASE)).json()
}
- Luego la función para crear un nuevo usuario, que recibe un usuario y retorna una promesa que resuelve el nuevo usuario.
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()
}
- Otra función para editar un usuario, que recibe el usuario a editar y retorna una promesa que resuelve el usuario editado.
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()
}
- 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.
export const deleteUser = async (id: number): Promise<number> => {
const method = 'DELETE'
await fetch(`${URL_BASE}/${id}`, { method })
return id
}
Asi quedaría este archivo:
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.
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.
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
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.
Vamos a src/hooks/useUser.tsx y crearemos un nuevo custom hook para crear nuevos usuarios.
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.
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.
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.
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.
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
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)
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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.
export const useGetUsersObserver = () => {
const get_users = useGetUsers()
return {
...get_users,
data:[],
}
}
- Creamos un estado para manejar el arreglo de usuarios, y ese estado se lo asignamos a la prop data.
export const useGetUsersObserver = () => {
const get_users = useGetUsers()
const [users, setUsers] = useState<User[]>()
return {
...get_users,
data: users,
}
}
- 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
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,
}
}
- 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.
useEffect(() => {
const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })
}, [])
- 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.
useEffect(() => {
const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })
const unsubscribe = observer.subscribe(result => {
if (result.data) setUsers(result.data)
})
}, [])
- Recuerda que una buena practica es cancelar la suscripción cuando el componente se desmonte.
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.
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
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.
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
🔵 No olvides seguirme también en twitter: @Frankomtz361
📢 Demostración simple.
https://rq-state-management.netlify.app/
📢 Código fuente.
Posted on March 17, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.