La idea es crear cuatro diferentes componentes, y cada componente hará una tipo de petición (GET, POST, PUT, DELETE).
De antemano te menciono que habrá código repetido. Si tu deseas puedes refactorizar, pero yo no lo hare porque mi propósito es enseñarte a usar axios!
Creamos la carpeta src/components y dentro creamos 4 archivos:
CreatePost.tsx
GetPost.tsx
UpdatePost.tsx
DeletePost.tsx
🚨 Nota: Cada vez que creamos una nueva carpeta, también crearemos un archivo index.ts para agrupar y exportar todas las funciones y componentes de otros archivos que están dentro de la misma carpeta, y que dichas funciones puedan ser importadas a traves de una sola referencia a esto se le conoce como archivo barril.
🕯️ ¿Qué es axios?.
Axios es una librería cliente HTTP basada en promesas que se puede usar tanto en Node JS como en el navegador; por lo que podremos configurar y realizar solicitudes a un servidor y recibiremos respuestas fáciles de procesar.
Nos ayuda en el envió de peticiones asíncronas HTTP, asi ayudándonos a realizar las operaciones CRUD.
Ademas es una buena alternativa al Fetch API por defecto de los navegadores.
🕯️ Creando una nueva instancia de axios.
Primero instalamos axios:
npm install axios
Vamos a crear la carpeta src/api y creamos el archivo client.ts
Dentro del archivo, vamos a importar axios y vamos a crear una nueva instancia de axios (la mayoría de las veces siempre vas a querer hacer esto).
Esto nos ayuda a tener una configuración centralizada en un lugar y personalizada, ya que la mayoría de los casos no queremos andar colocando la misma configuración cada vez que hagamos una petición.
La propiedad create de axios recibe un objeto de configuración, donde en este caso de pasamos la propiedad baseURL, que es la url base a la que haremos las peticiones
También le pueden establecer los headers, el timeout (numero que indica el tiempo en milisegundos para que la petición sea abortada), y otras propiedades, pero solo con el base URL nos basta para este ejemplo.
Vamos a usar la API de JSONPlaceholder para hacer las peticiones.
Y de una vez definimos, aquí mismo, la interfaz de la respuesta que nos va a dar la API.
Primero vamos a src/App.tsx y vamos a importar todos los componentes que creamos con anterioridad.
Vamos a colocar primero el GetPost y luego lo comentaremos para poner el siguiente y asi sucesivamente.
Ahora dentro de src/components en el archivo GetPost.tsx, vamos a crear un componente que va a mostrar unas tarjetas que provienen de la API.
Primero tenemos un estado que almacena los post que sera un arreglo.
Después, tenemos el JSX que solo mostramos unos textos y recorremos la variable de estado posts y renderizamos la información que nos debería dar la API.
import{useState}from'react';import{ResponseAPI}from'../api';exportconstGetPost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])return (<><h1>Get Post 👇</h1><br/><h2>posts list</h2><divclassName='grid'>{posts.map(post=>(<divkey={post.id}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></>)}
Luego, vamos a crear una nueva carpeta src/utils donde crearemos varios archivos, el primero es getData.ts
Para usar axios es muy sencillo, solamente importamos la instancia que creamos.
Primero creamos una función, que sera asíncrona y retornara una promesa que resuelve un arreglo de ResponseAPI.
Nota que ejecutamos la función get y dentro colocamos unas comillas vacías, ¿por que?
Bueno esto es necesario para hacerle saber a la instancia que no queremos agregar mas parámetros a nuestra URL base.
Porque por el momento tenemos la URL asi: "https://jsonplaceholder.typicode.com/posts", por lo que agreguemos dentro del método get (o cualquier otro método) se concatenara a la esta URL.
Por eso solo colocamos comillas simples y vacías haciendo referencia a que no queremos concatenar nada.
Pero si queremos concatenar algo como por ejemplo el limite de resultados de la API lo colocaremos de la siguiente manera:
Ahora, esta promesa nos devuelve un objeto que contiene varias propiedades como la configuración, los headers, el estado de la petición, entre otros pero el que nos interesa es la propiedad data. La propiedad data es lo que vamos a retornar, porque es ahi donde vendrá la respuesta de la API.
Nota que también esta tipado el método get con ResponseAPI[], por si las dudas, y aunque no loe pongas el tipo y retornes la data, funcionara porque la propiedad data por defecto es de tipo any.
import{client,ResponseAPI}from"../api"exportconstgetPosts=async ():Promise<ResponseAPI[]>=>{const{data}=awaitclient.get<ResponseAPI[]>('?_limit=6')
return data
}
Asi de fácil es hacer una petición GET.
Ahora devuelta a src/components/GetPost.tsx
Implementamos un efecto para hacer la llamada a la API (en esta ocasión la haremos asi, aunque lo recomendable es usar una librería para manejar el cache de las peticiones como React Query)
Dentro del efecto ejecutamos la función getPosts y resolvemos la promesa para después establecer la data que nos regresa dicha promesa.
import{useState,useEffect}from'react';import{ResponseAPI}from'../api';import{getPosts}from'../utils';exportconstGetPost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{// You can implement a <Loading/>// start loadinggetPosts().then(data=>setPosts(data))// finish loading},[])return (<><h1>Get Post 👇</h1><br/><h2>posts list</h2><divclassName='grid'>{posts.map(post=>(<divkey={post.id}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></>)}
Y asi luciría la data de la API ya renderizada
🕯️ Creando la petición POST.
Ahora vamos al archivo src/components/CreatePost.tsx y creamos un nuevo componente similar al anterior.
Este componente renderiza la misma lista de post y los almacena en un estado también.
Nota que la key, al momento de recorrer el estado de posts, es post.userId porque este es el único valor que es diferente cuando creamos un nuevo post.
También nota que se agrego un botón, para crear post, lo haremos sin un formulario, pero lo mejor seria recibir los valores por un formularios.
Este botón, en su evento onClick ejecuta la función handleClick, que por el momento no hace nada, pero ahi es donde tenemos que ejecutar le método para crear un nuevo post.
import{useState}from'react';import{ResponseAPI}from'../api';exportconstCreatePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])consthandleClick=async ()=>{}return (<div><h1>Create Post 👇</h1><buttononClick={handleClick}>Add Post</button><divclassName='grid'>{posts.map(post=>(<divkey={post.userId}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></div>)}
Después, vamos a crear un nuevo archivo en src/utils nombrado createPost.ts
Dentro del archivo creamos una nueva función que nos retorne una promesa resolviendo un solo ResponseAPI. Y también, esta función recibe algunos parámetros que son los necesarios para crear un nuevo post.
Dentro de la función llamamos a la instancia y ejecutamos su método post, el cual también nos da acceso, una vez resuelta la promesa, a el objeto data.
Nota que también debemos indicarle, si se necesita concatenar a la URL base o no, en este caso no, por eso solo colocamos comillas vacías
Pero el método post no solo recibe la URL, sino que también necesita el body o sea la data a enviar para crear nueva información.
Asi que ese body, se lo pasamos como segundo parámetro, en un objeto.
Listo, ya tenemos nuestra función para crear un nuevo post, ahora vamos a usarla en src/components/CreatePost.tsx
En la función handleClick llamamos a la función createPost y le pasamos los parámetros necesarios. Usamos async/await para resolver la promesa que nos retorna la función createPost, que si todo sale correcto, nos debe retornar un nuevo post.
Este nuevo post, lo vamos agregar al estado, manteniendo los posts anteriores.
import{useState}from'react';import{ResponseAPI}from'../api';import{createPost}from'../utils';exportconstCreatePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])consthandleClick=async ()=>{constnewPost=awaitcreatePost("new title","something",Date.now())setPosts(prev=>([newPost,...prev]))}return (<div><h1>Create Post 👇</h1><buttononClick={handleClick}>Add Post</button><divclassName='grid'>{posts.map(post=>(<divkey={post.userId}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></div>)}
Probablemente no veamos nada, pero recuerden comentar el anterior componente y colocar el nuevo, en src/App.tsx
Luego vamos a src/components/UpdatePost.tsx para crear un nuevo componente funcional que es igual al de GetPost.tsx. ya que necesitamos una lista de posts existentes para poder actualizar alguno.
Nota que el div que se renderiza al momento de recorrer los post:
Tiene un className='card'
Tiene un evento onClick que ejecuta la función handleUpdate y le mandamos el id del post como parámetro.
La función handleUpdate es asíncrona y recibe el id del post que es de tipo numero, y por el momento no ejecuta nada.
import{useState,useEffect}from'react';import{ResponseAPI}from'../api';import{getPosts}from'../utils';exportconstUpdatePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{getPosts().then(data=>setPosts(data))},[])consthandleUpdate=async (id:number)=>{}return (<div><h1>Update Post 👇</h1><br/><h2>Click a card</h2><divclassName='grid'>{posts.map(post=>(<divclassName='card'key={post.id}onClick={()=>handleUpdate(post.id)}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></div>)}
Después, vamos a src/utils creamos un nuevo archivo updatePost.ts
Donde básicamente es casi igual que el método post.
Nota que ahora los parámetros de la función están definidas en una interfaz.
La única diferencia entre el post y el put es que en la URL si tenemos que colocar un nuevo parámetro que es el id del post que queremos modificar.
Ahora vamos a usar nuestra función, en src/components/UpdatePost.tsx
El la función handleUpdate llamamos a la función updatePost y le pasamos los parámetros necesarios, nuevamente usamos async/await para resolver la promesa y obtener el post actualizado.
Finalmente establecemos un nuevo estado, colocando el post actualizado.
import{useState,useEffect}from'react';import{ResponseAPI}from'../api';import{getPosts,updatePost}from'../utils';exportconstUpdatePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{getPosts().then(data=>setPosts(data))},[])consthandleUpdate=async (id:number)=>{constbody=`Body updated`consttitle=`Title updated`constuserId=Date.now()constpostUpdated=awaitupdatePost({id,body,title,userId})setPosts(prev=>([postUpdated,...prev.filter(post=>post.id!==id),]))}return (<div><h1>Update Post 👇</h1><br/><h2>Click a card</h2><divclassName='grid'>{posts.map(post=>(<divclassName='card'key={post.id}onClick={()=>handleUpdate(post.id)}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></div>)}
🕯️ Creando la petición DELETE.
Primero vamos a cambiar el componente en src/App.tsx
Luego vamos a src/components/DeletePost.tsx para crear un nuevo componente funcional que es igual al de UpdatePost.tsx. ya que necesitamos una lista de posts existentes para poder eliminar alguno.
Y ahora tenemos la función handleDelete que recibe un id y por el momento no hace nada.
Nota que ahora el evento onClick en cada post, ejecuta una función llamada handleDelete y se le pasa el id del post
import{getPosts}from"../utils"import{useEffect,useState}from'react';import{ResponseAPI}from"../api";exportconstDeletePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{getPosts().then(data=>setPosts(data))},[])consthandleDelete=async (id:number)=>{}return (<><h1>Delete Post 👇</h1><br/><h2>Click a card</h2><divclassName="grid">{posts.map(post=>(<divclassName="card"key={post.id}onClick={()=>handleDelete(post.id)}><p>ID: <span>{post.id}</span></p><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p></div>))}</div></>)}
Entonces necesitamos crear un nuevo archivo src/utils nombrado deletePost.ts que adentro tendrá una función asíncrona que solo retornara una promesa resolviendo un valor booleano para indicar si la eliminación del post se hizo correctamente.
Solamente llamamos al método delete de nuestra instancia y agregamos el ID del post que vamos a eliminar.
En ese caso disponemos de las mismas propiedades que cuando ejecutamos un get, post o put. Pero la data nos nos sirve porque llegaría un objeto vació, en este caso evaluaremos el código de estado de la petición, si es un estado 200 significa que todo salio bien entonces retornaría un true.
Ahora vamos a usar esta función en src/components/DeletePost.tsx
Dentro de la función handleDelete ejecutamos la función deletePost mandando el ID del post que queremos eliminar, y mediante async/await resolvemos la promesa para obtener el valor booleano y en base a ese valor, actualizar el estado.
import{deletePost,getPosts}from"../utils"import{useEffect,useState}from'react';import{ResponseAPI}from"../api";exportconstDeletePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{getPosts().then(data=>setPosts(data))},[])consthandleDelete=async (id:number)=>{constisSuccess=awaitdeletePost(id)if (isSuccess)setPosts(prev=>prev.filter(post=>post.id!==id))}return (<><h1>Delete Post 👇</h1><br/><h2>Click a card</h2><divclassName="grid">{posts.map(post=>(<divclassName="card"key={post.id}onClick={()=>handleDelete(post.id)}><p>ID: <span>{post.id}</span></p><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p></div>))}</div></>)}
🕯️ Manejando errores.
Manejar los errores con axios, solamente basta con ir a nuestras funciones helper (src/utils/) y usar un try/catch ya que hemos estado usando async/await.
Dentro del try colocamos todo el código que queremos ejecutar.
Dentro del catch, vamos a recibir el error si es que algo sale mal, por ejemplo: error en la solicitud, error en el servidor, entro otros.
Este error que recibimos por parámetro, lo podemos castear a un tipo que no ofrece axios que es AxiosError y asi tener el autocompletado.
Este error tiene varias propiedades, pero la mas común es el mensaje de error y el nombre del error.
Finalmente nota que en el catch retornamos un arreglo vació, esto es para cumplir con el contrato de la función ya que debemos devolver una promesa tipo ResponseAPI[]
Sin duda axios es una librería muy potente, aunque no te estoy diciendo que sea de uso obligatorios, es una librería muy util que te servirá en algún proyecto. 😉
Espero que te haya gustado esta publicación y que también espero haberte ayudado a entender como realizar peticiones básicas HTTP con esta librería, una alternativa a Fetch API. 🙌
Si conoces alguna otra forma distinta o mejor de realizar esta aplicación con gusto puedes comentarla.
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.
Hice unos cambios en el App.tsx para que no tengas que ir al código y comentar el componente y probar cada tipo de petición