DEV Community

Cover image for React.js API Handling
Rifky Alfarez
Rifky Alfarez

Posted on

React.js API Handling

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>&nbsp;|&nbsp;</span>
            <EditProduct
              id={product.id}
              prevTitle={product.title}
              prevDescription={product.description}
              prevPrice={product.price}
              prevImage={product.image}
              prevCategory={product.category}
            />
          </div>
        ))}
      </div>
      <AddProduct />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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)