DEV Community

Dharmesh
Dharmesh

Posted on

# Managing CRUD Operations in React using Context API & TypeScript

Hi Devs ๐Ÿ‘‹,

In this post, I want to share how to manage CRUD (Create, Read, Update, Delete) operations in a React app using Context API and TypeScript, with separate inputs for creating new items and updating existing items.


Scenario

  • We have a list of items fetched from an API.
  • Users can create new items.
  • Users can update an existing item using a separate input field.
  • Users can delete items.
  • All state is centralized in a Context, keeping UI components clean.

Define Interfaces

// src/types.ts
export interface Item {
  id: number;
  name: string;
}

export interface EditingItem {
  id: number;
  name: string;
}

Enter fullscreen mode Exit fullscreen mode

Context Setup

// src/context/ItemContext.tsx
import React, { createContext, useContext, useEffect, useState } from "react";
import { Item } from "../types";

interface ItemContextType {
  items: Item[];
  fetchItems: () => Promise<void>;
  createItem: (name: string) => Promise<void>;
  updateItem: (id: number, name: string) => Promise<void>;
  deleteItem: (id: number) => Promise<void>;
}

const ItemContext = createContext<ItemContextType | null>(null);

export const ItemProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [items, setItems] = useState<Item[]>([]);

  const fetchItems = async () => {
    // Replace with actual API call
    const res = await fetch("/api/items");
    const data = await res.json();
    setItems(data);
  };

  const createItem = async (name: string) => {
    const res = await fetch("/api/items", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name }),
    });
    const newItem = await res.json();
    setItems((prev) => [...prev, newItem]);
  };

  const updateItem = async (id: number, name: string) => {
    const res = await fetch(`/api/items/${id}`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name }),
    });
    const updated = await res.json();
    setItems((prev) => prev.map((item) => (item.id === id ? updated : item)));
  };

  const deleteItem = async (id: number) => {
    await fetch(`/api/items/${id}`, { method: "DELETE" });
    setItems((prev) => prev.filter((item) => item.id !== id));
  };

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

  return (
    <ItemContext.Provider value={{ items, fetchItems, createItem, updateItem, deleteItem }}>
      {children}
    </ItemContext.Provider>
  );
};

export const useItems = () => {
  const context = useContext(ItemContext);
  if (!context) throw new Error("useItems must be used inside ItemProvider");
  return context;
};

Enter fullscreen mode Exit fullscreen mode

Component with Separate Inputs

// src/pages/IndexPage.tsx
import React, { useState } from "react";
import { useItems } from "../context/ItemContext";
import { EditingItem, Item } from "../types";

const IndexPage: React.FC = () => {
  const { items, createItem, updateItem, deleteItem } = useItems();

  // Create Input
  const [newName, setNewName] = useState<string>("");

  // Update Input
  const [editingItem, setEditingItem] = useState<EditingItem | null>(null);
  const [editName, setEditName] = useState<string>("");

  return (
    <div style={{ padding: "20px" }}>
      {/* ===================== CREATE ===================== */}
      <h2>Create New Item</h2>
      <input
        type="text"
        value={newName}
        onChange={(e) => setNewName(e.target.value)}
        placeholder="Enter new item"
      />
      <button
        onClick={() => {
          if (newName.trim() === "") return;
          createItem(newName);
          setNewName("");
        }}
      >
        Add
      </button>

      {/* ===================== UPDATE ===================== */}
      <h2 style={{ marginTop: "30px" }}>Update Item</h2>
      {editingItem ? (
        <>
          <input
            type="text"
            value={editName}
            onChange={(e) => setEditName(e.target.value)}
            placeholder="Update item name"
          />
          <button
            onClick={() => {
              if (editName.trim() === "") return;
              updateItem(editingItem.id, editName);
              setEditingItem(null);
              setEditName("");
            }}
          >
            Update
          </button>
          <button
            onClick={() => {
              setEditingItem(null);
              setEditName("");
            }}
            style={{ marginLeft: "10px" }}
          >
            Cancel
          </button>
        </>
      ) : (
        <p>Select an item to edit from the list below</p>
      )}

      {/* ===================== LIST ===================== */}
      <h2 style={{ marginTop: "30px" }}>Items List</h2>
      <ul>
        {items.map((item: Item) => (
          <li key={item.id}>
            {item.name}{" "}
            <button
              onClick={() => {
                setEditingItem({ id: item.id, name: item.name });
                setEditName(item.name);
              }}
            >
              Edit
            </button>{" "}
            <button onClick={() => deleteItem(item.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default IndexPage;

Enter fullscreen mode Exit fullscreen mode

Top comments (0)