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 (0)