Content

  • React Hooks Summary
  • React + ASP.NET Core Web API - CRUD

React Hooks Summary

Hook Purpose Example Short Explanation
useState Store and manage component data const [products, setProducts] = useState([]); Used to store values that can change, such as form data or API response.
useEffect Run code on component load or update useEffect(() => { loadProducts(); }, []); Executes code when the component loads or when dependencies change.
useRef Access or control elements without re-render const loadingRef = useRef(null); Used to control the loading bar without triggering component re-render.
useLocation Detect route (URL) changes const location = useLocation(); Used to trigger actions like loading bar when navigation changes.

React + ASP.NET Core Web API - CRUD

In this practical, we integrate a React frontend with an ASP.NET Core Web API to perform complete CRUD (Create, Read, Update, Delete) operations.

CORS Configuration

React runs on http://localhost:5173 and the API runs on a different port. Due to browser security rules, the API must explicitly allow requests from React.

Example: Program.cs – Enable CORS

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowReactApp",
        builder =>
        {
            builder
                .WithOrigins("http://localhost:5173")
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
});
app.UseRouting();
app.UseCors("AllowReactApp");

Frontend Folder Structure

src/
 ├── components/
 │    ├── Products.jsx
 │    ├── ProductForm.jsx
 ├── services/
 │    └── productService.js
 ├── App.jsx

productService – API Communication Layer

The productService file contains all API calls. This keeps React components clean and reusable.

const API_URL = "https://localhost:7079/api/ProductDB";

export const getProducts = async () => {
  const response = await fetch(API_URL);
  return await response.json();
};

export const addProduct = async (product) => {
  await fetch(API_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(product)
  });
};

export const updateProduct = async (id, product) => {
  await fetch(`${API_URL}/${id}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(product)
  });
};

export const deleteProduct = async (id) => {
  await fetch(`${API_URL}/${id}`, {
    method: "DELETE"
  });
};
  • APIs cannot understand JavaScript objects directly
  • JSON.stringify() converts a JavaScript object into JSON text
  • "Content-Type": "application/json" tells the server the data format, and JSON.stringify() converts JavaScript objects into JSON before sending them to the API.

Products Component (Read & Delete)

import { useEffect, useState } from "react";
import { getProducts, deleteProduct } from "../services/productService";
import { toast } from "react-toastify";

function Products({ onEdit }) {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    loadProducts();
  }, []);

  const loadProducts = async () => {
    const data = await getProducts();
    setProducts(data);
  };

  const handleDelete = async (id) => {
    if (!window.confirm("Delete product?")) return;
    await deleteProduct(id);
    toast.success("Product deleted");
    loadProducts();
  };

  return (
    <table className="table table-bordered">
      <tbody>
        {products.map(p => (
          <tr key={p.id}>
            <td>{p.name}</td>
            <td>{p.price}</td>
            <td>
              <button onClick={() => onEdit(p)}>Edit</button>
              <button onClick={() => handleDelete(p.id)}>Delete</button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

export default Products;

Products Component – Important Points

  • Uses useState to store the list of products fetched from the API.
  • Uses useEffect to load product data when the component is first rendered.
  • Fetches data from backend using getProducts() service method.
  • Displays product data using the map() loop inside a table.
  • Uses a unique key (product id) for efficient rendering.

Note on onEdit Prop

  • onEdit is a callback function passed from the parent component to the Products component.
  • It is triggered when the user clicks the Edit button for a product.
  • The Products component does not edit the product itself.
  • When onEdit(product) is called, the selected product is sent back to the parent component.
  • The parent component stores the selected product in its state.
  • This selected product is then passed to the ProductForm component for editing.
  • Using onEdit follows proper parent–child communication in React.
  • This approach keeps responsibilities separate and avoids direct component dependency.

ProductForm Component (Create & Update)

import { useEffect, useState } from "react";
import { addProduct, updateProduct } from "../services/productService";
import { toast } from "react-toastify";

function ProductForm({ selectedProduct, onSaved }) {

  const [product, setProduct] = useState({ name: "", price: "" });

  useEffect(() => {
    if (selectedProduct) {
      setProduct(selectedProduct);
    }
  }, [selectedProduct]);

  const handleChange = (e) => {
    setProduct({
      ...product,
      [e.target.name]: e.target.value
    });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    if (!product.name || !product.price) {
      toast.error("All fields required");
      return;
    }

    if (product.id) {
      await updateProduct(product.id, product);
      toast.success("Product updated");
    } else {
      await addProduct(product);
      toast.success("Product added");
    }

    onSaved();
    setProduct({ name: "", price: "" });
  };

  return (
    <form onSubmit={handleSubmit}>

      <input
        name="name"
        value={product.name}
        onChange={handleChange}
        placeholder="Product Name"
      />

      <input
        name="price"
        value={product.price}
        onChange={handleChange}
        placeholder="Price"
      />

      <button type="submit">Save</button>

    </form>
  );
}

export default ProductForm;
  • The value attribute binds the input field to React state.
  • This makes the input a controlled component.
  • The onChange event is triggered whenever the user types.
  • onChange updates the state using setProduct().
  • Without onChange, inputs with value become read-only.
  • The handleChange function updates the correct field using the input name.
  • This ensures the UI and state are always synchronized.

Why Spread Operator (...) Is Used

  • The spread operator (...) copies existing object properties.
  • It prevents loss of other values while updating one field.
  • React state objects must be updated immutably.
  • Without it, only one field would remain and others would be removed.
  • This is a common pattern used in React forms.

ProductForm Component – Important Points

  • ProductForm is used for both Add and Edit operations.
  • It uses useState to store form values such as product name and price.
  • The form fields are controlled components, meaning their values are managed by React state.
  • The component receives selectedProduct as a prop from the parent component.
  • useEffect is used to populate the form when a product is selected for editing.
  • On form submission, it decides whether to add or update a product based on the presence of product.id.
  • It calls the backend API using addProduct() or updateProduct().

Note on onSaved Prop

  • onSaved is a callback function passed from the parent component.
  • It is called after a product is successfully added or updated.
  • The ProductForm component does not know what onSaved does internally.
  • Calling onSaved() informs the parent that the save operation is complete.
  • The parent component then refreshes the product list and clears the edit selection.
  • This approach avoids page refresh and follows proper React component communication.

Refreshing Data Without Page Reload

const [selectedProduct, setSelectedProduct] = useState(null);
const [refreshKey, setRefreshKey] = useState(0);

<ProductForm
        selectedProduct={selectedProduct}
        onSaved={() => {
          setSelectedProduct(null);
          setRefreshKey(prev => prev + 1);
        }}
      />

      <Products
        key={refreshKey}
        onEdit={(p) => setSelectedProduct(p)}
      />
  • refreshKey is a state variable used to force component reload.
  • Changing the value of refreshKey triggers React re-rendering.
  • The onSaved callback increments refreshKey after save or update.
  • The key prop is set to refreshKey on the Products component.
  • When the key changes, React recreates the Products component.
  • This causes useEffect to run again and reload data.
  • This approach avoids manual page refresh.