In this article, I’d like to share my experience handling API requests in React.js without using any external libraries. It includes working with HTTP methods like GET, POST, PATCH, and DELETE while implementing features such as search, pagination, sorting, and filtering. This approach helped me better understand the core concepts of React.js and API handling. I hope my insights can be useful for anyone exploring similar implementations.
I won’t be giving any explanations—just the code itself. So, let the code speak for itself!
App.tsx
import { useEffect, useState } from 'react'; import AddProduct from './components/AddProduct'; import DeleteProduct from './components/DeleteProduct'; import EditProduct from './components/EditProduct'; interface Product { id: number; title: string; description: string; category: string; price: number; image: string; } export const BASE_URL = 'https://fakestoreapi.com/products'; export default function App() { const [products, setProducts] = useState<Product[]>([]); const [query, setQuery] = useState<string>(''); const [category, setCategory] = useState<string>(''); const [sortBy, setSortBy] = useState<string>(''); const [orderBy, setOrderBy] = useState<string>(''); const [limit, setLimit] = useState<number>(10); const [currentPage, setCurrentPage] = useState<number>(1); const totalPages = Math.ceil(products.length / limit); const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const term = e.target.value; if (!term.startsWith(' ')) setQuery(term); }; const handlePrev = () => { if (currentPage > 1) setCurrentPage(currentPage - 1); }; const handleNext = () => { if (currentPage < totalPages) setCurrentPage(currentPage + 1); }; useEffect(() => { const fetchProducts = async () => { const response = await fetch(BASE_URL); const result = (await response.json()) as Product[]; const filteredProducts = result .filter((product) => product.title.toLocaleLowerCase().includes(query.toLocaleLowerCase()) ) .filter((product) => (category ? product.category === category : true)) .sort((a, b) => { if (sortBy === 'title') { return orderBy === 'des' ? b.title.localeCompare(a.title) : a.title.localeCompare(b.title); } if (sortBy === 'price') { return orderBy === 'des' ? b.price - a.price : a.price - b.price; } return orderBy === 'des' ? b.id - a.id : a.id - b.id; }); setProducts(filteredProducts); setCurrentPage(1); }; fetchProducts(); }, [category, query, sortBy, orderBy]); const paginatedProducts = products.slice( (currentPage - 1) * limit, currentPage * limit ); return ( <div className="p-4"> <div className="flex items-center gap-x-4"> <input type="search" className="border border-neutral-700" placeholder="Search..." onChange={handleSearch} value={query} /> <select defaultValue="" onChange={(e) => setCategory(e.target.value)} className="border border-neutral-700" > <option value="">Select Category:</option> <option value="jewelery">Jewelery</option> <option value="electronics">Electronics</option> <option value="men's clothing">Men's clothing</option> <option value="women's clothing">Women's clothing</option> </select> <select defaultValue="" onChange={(e) => setSortBy(e.target.value)} className="border border-neutral-700" > <option value="">Sort By:</option> <option value="title">Title</option> <option value="price">Price</option> </select> <select defaultValue="" onChange={(e) => setOrderBy(e.target.value)} className="border border-neutral-700" > <option value="">Order By:</option> <option value="asc">Ascending</option> <option value="des">Descending</option> </select> <select className="border border-neutral-700" defaultValue="" onChange={(e) => setLimit(Number(e.target.value))} > <option value={10} defaultValue={10}> Limit: </option> {Array.from({ length: 20 }).map((_, i) => ( <option key={i} value={i + 1}> {i + 1} </option> ))} </select> </div> <div className="flex items-center gap-x-2 mt-4"> <button onClick={handlePrev} disabled={currentPage === 1} className="border border-neutral-700 disabled:opacity-50" > Prev </button> <span> page {currentPage} of {totalPages || 1} </span> <button onClick={handleNext} disabled={currentPage === totalPages} className="border border-neutral-700 disabled:opacity-50" > Next </button> </div> <div className="grid grid-cols-4 gap-4 mt-4"> {paginatedProducts.map((product) => ( <div key={product.id} className="border border-neutral-700 p-4"> <p>{product.title}</p> <p>{product.category}</p> <p>${product.price}</p> <DeleteProduct id={product.id} /> <span> | </span> <EditProduct id={product.id} prevTitle={product.title} prevDescription={product.description} prevPrice={product.price} prevImage={product.image} prevCategory={product.category} /> </div> ))} </div> <AddProduct /> </div> ); }
AddProduct.tsx
import { useState } from 'react'; import { BASE_URL } from '../App'; export default function AddProduct() { const [title, setTitle] = useState<string>(''); const [price, setPrice] = useState<number | ''>(''); const [description, setDescription] = useState<string>(''); const [image, setImage] = useState<string>(''); const [category, setCategory] = useState<string>('jewelery'); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!title || !price || !description || !image || !category) { alert('Please fill out all fields'); return; } try { const response = await fetch(`${BASE_URL}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title, price, description, image, category, }), }); const result = await response.json(); console.log('Product created:', result); setTitle(''); setPrice(''); setDescription(''); setImage(''); setCategory('jewelery'); alert('Product added successfully!'); } catch (error) { console.log('Error creating product', error); alert('Failed create product'); } }; return ( <form onSubmit={handleSubmit} className="flex flex-col gap-y-2 w-[300px] mt-4" > <input type="text" placeholder="Title" className="border border-neutral-700" value={title} onChange={(e) => setTitle(e.target.value)} /> <input type="number" placeholder="Price" className="border border-neutral-700" value={price} onChange={(e) => setPrice(Number(e.target.value))} /> <textarea placeholder="Description" className="border border-neutral-700" value={description} onChange={(e) => setDescription(e.target.value)} /> <input type="url" placeholder="Image" className="border border-neutral-700" value={image} onChange={(e) => setImage(e.target.value)} /> <select className="border border-neutral-700" onChange={(e) => setCategory(e.target.value)} value={category} > <option value="jewelery">Jewelery</option> <option value="electronics">Electronics</option> <option value="men's clothing">Men's clothing</option> <option value="women's clothing">Women's clothing</option> </select> <button type="submit" className="bg-neutral-800 text-white"> Create </button> </form> ); }
DeleteProduct.tsx
import { BASE_URL } from '../App'; export default function DeleteProduct({ id }: { id: number }) { const handleDelete = async () => { try { const response = await fetch(`${BASE_URL}/${id}`, { method: 'DELETE', }); const result = await response.json(); console.log('Product Deleted Succesfully', result); alert('Product has been delete'); } catch (error) { console.log('Failed Delete Product', error); alert('Failed Delete Product'); } }; return <button onClick={handleDelete}>Delete</button>; }
EditProduct.tsx
import { useState } from 'react'; import { BASE_URL } from '../App'; export default function EditProduct({ id, prevTitle, prevPrice, prevDescription, prevImage, prevCategory, }: { id: number; prevTitle: string; prevPrice: number; prevDescription: string; prevImage: string; prevCategory: string; }) { const [title, setTitle] = useState<string>(prevTitle); const [price, setPrice] = useState<number | ''>(prevPrice); const [description, setDescription] = useState<string>(prevDescription); const [image, setImage] = useState<string>(prevImage); const [category, setCategory] = useState<string>(prevCategory); const [open, setOpen] = useState<boolean>(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!title || !price || !description || !image || !category) { alert('Please fill out all fields'); return; } try { const response = await fetch(`${BASE_URL}/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title, price, description, image, category, }), }); const result = await response.json(); console.log('Product edited:', result); alert('Product edited successfully!'); } catch (error) { console.log('Error editing product', error); alert('Failed edit product'); } }; return ( <> <button onClick={() => { setOpen(!open); }} > Edit </button> <form onSubmit={handleSubmit} className={`flex flex-col gap-y-2 w-[300px] mt-4 ${ open ? '' : 'hidden' }`} > <input type="text" placeholder="Title" className="border border-neutral-700" value={title} defaultValue={title} onChange={(e) => setTitle(e.target.value)} /> <input type="number" placeholder="Price" className="border border-neutral-700" value={price} defaultValue={price} onChange={(e) => setPrice(Number(e.target.value))} /> <textarea placeholder="Description" className="border border-neutral-700" defaultValue={description} value={description} onChange={(e) => setDescription(e.target.value)} /> <input type="url" placeholder="Image" className="border border-neutral-700" defaultValue={image} value={image} onChange={(e) => setImage(e.target.value)} /> <select className="border border-neutral-700" onChange={(e) => setCategory(e.target.value)} value={category} defaultValue={category} > <option value="jewelery">Jewelery</option> <option value="electronics">Electronics</option> <option value="men's clothing">Men's clothing</option> <option value="women's clothing">Women's clothing</option> </select> <button type="submit" className="bg-neutral-800 text-white"> Edit </button> </form> </> ); }
Conclusion
Thank you for taking the time to check out this article. I hope the code examples provided some useful insights into handling APIs in React.js without external libraries. If you’re interested in exploring the complete project, feel free to visit the code repository: https://github.com/rfkyalf/reactjs-api-handling.
If you have any suggestions or questions, feel free to share them in the comments section. I’m always open to feedback and discussions!
I’m always excited to connect and collaborate on web development projects. You can learn more about my work and past projects by visiting my portfolio website: https://www.rifkyalfarez.my.id.
Top comments (2)
import React from 'react';
import { useNavigate } from 'react-router-dom';
function Navbar() {
const navigate = useNavigate();
const user = JSON.parse(localStorage.getItem('user')); // read data from localStorage
}
export default Navbar;
it’s easy to understand!🫵❤️🔥