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;
}
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;
};
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;
Top comments (0)