In this article we will create the CRUD CRUD project in TypeScript using Vite and JSON Server. TypeScript adds static typing to JavaScript, helping you catch errors early and ensuring type safety.
Step 1: Set up the TypeScript Project
Create a Vite project with TypeScript template: Open your terminal and run the following command to create a new project:
npm create vite@latest crud-app-ts --template react-ts
cd crud-app-ts
Install necessary dependencies: We will need axios
for API requests and json-server
for the mock backend:
npm install axios
npm install json-server
Create a db.json
file: Similar to the JavaScript version, create a db.json
file at the root of your project with the following content:
{
"items": [
{ "id": 1, "name": "Item 1", "description": "This is item 1" },
{ "id": 2, "name": "Item 2", "description": "This is item 2" },
{ "id": 3, "name": "Item 3", "description": "This is item 3" }
]
}
Start the JSON Server: Run the following command to start the JSON server on port 5000:
npx json-server --watch db.json --port 5000
Step 2: Set up TypeScript React Components
Edit
src/App.tsx
: We’ll create the React components and ensure proper type definitions for our state and props.Replace the content of
src/App.tsx
with the following code:
// Import necessary modules from React and Axios
import React, { useState, useEffect, ChangeEvent } from 'react'; // Import useState and useEffect for managing state and lifecycle, ChangeEvent for typing events
import axios from 'axios'; // Axios is used for making HTTP requests
// Define an interface to type the structure of our items
interface Item {
id: number; // Unique identifier for each item
name: string; // Name of the item
description: string; // Description of the item
}
const App: React.FC = () => {
// State to hold the list of items fetched from the server
const [items, setItems] = useState<Item[]>([]); // items is an array of Item objects
// State to manage the new item being added (doesn't have an id yet)
const [newItem, setNewItem] = useState<Omit<Item, 'id'>>({ name: '', description: '' });
// Omit<Item, 'id'> means we are creating an object with all properties of Item, except 'id', since the id is automatically generated by the server.
// State to manage the item being edited (null if no item is being edited)
const [editItem, setEditItem] = useState<Item | null>(null);
// This can either hold an item object (for editing) or null (when nothing is being edited).
// useEffect is a hook that runs side-effects; here, it runs when the component mounts
// Fetches all items from the server when the component is first loaded
useEffect(() => {
axios.get<Item[]>('http://localhost:5000/items') // GET request to fetch all items
.then(response => {
setItems(response.data); // Sets the items state with the data fetched from the server
})
.catch(error => {
console.error('Error fetching items:', error); // Logs an error if the request fails
});
}, []); // The empty array ensures this effect only runs once, after the initial render
// Handle changes in the input fields for adding a new item
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target; // Destructure the name and value of the changed input field
setNewItem((prevState) => ({ ...prevState, [name]: value }));
// Spread the previous state (...prevState) and update only the changed field ([name]: value)
};
// Function to add a new item to the list
const addItem = () => {
axios.post<Item>('http://localhost:5000/items', newItem)
// POST request to add a new item to the server
.then(response => {
setItems([...items, response.data]);
// Add the newly created item (response.data) to the list of items
setNewItem({ name: '', description: '' });
// Clear the input fields after the item is added
})
.catch(error => {
console.error('Error adding item:', error); // Log an error if the request fails
});
};
// Start editing an existing item
const startEdit = (item: Item) => {
setEditItem(item); // Set the editItem state to the selected item, allowing it to be edited
};
// Save the edited item
const saveEdit = () => {
if (!editItem) return; // If no item is selected for editing, return early
axios.put<Item>(`http://localhost:5000/items/${editItem.id}`, editItem)
// PUT request to update the item on the server with the new data
.then(response => {
// Map through the items array and replace the old item with the updated one
const updatedItems = items.map(item =>
item.id === editItem.id ? response.data : item // If the item id matches, replace it with the updated item
);
setItems(updatedItems); // Update the state with the new list of items
setEditItem(null); // Clear the editItem state after saving the changes
})
.catch(error => {
console.error('Error updating item:', error); // Log an error if the request fails
});
};
// Delete an item from the list
const deleteItem = (id: number) => {
axios.delete(`http://localhost:5000/items/${id}`)
// DELETE request to remove an item from the server by its id
.then(() => {
setItems(items.filter(item => item.id !== id));
// Update the items state to remove the deleted item from the list
})
.catch(error => {
console.error('Error deleting item:', error); // Log an error if the request fails
});
};
// JSX return statement: renders the UI
return (
<div>
<h1>CRUD Application with TypeScript</h1>
<h2>Items</h2>
<ul>
{items.map((item) => (
<li key={item.id}>
{editItem?.id === item.id ? (
// Check if the item is currently being edited (editItem.id === item.id)
<div>
<input
name="name"
value={editItem.name}
onChange={(e) =>
setEditItem({ ...editItem, name: e.target.value })
// Update the name field of the item being edited
}
/>
<input
name="description"
value={editItem.description}
onChange={(e) =>
setEditItem({ ...editItem, description: e.target.value })
// Update the description field of the item being edited
}
/>
<button onClick={saveEdit}>Save</button>
{/* Button to save the edited item */}
<button onClick={() => setEditItem(null)}>Cancel</button>
{/* Button to cancel editing */}
</div>
) : (
<div>
{item.name} - {item.description}
{/* Display the item name and description */}
<button onClick={() => startEdit(item)}>Edit</button>
{/* Button to start editing the selected item */}
<button onClick={() => deleteItem(item.id)}>Delete</button>
{/* Button to delete the selected item */}
</div>
)}
</li>
))}
</ul>
<h2>Add New Item</h2>
<input
name="name"
value={newItem.name}
onChange={handleChange}
// Input field for the name of the new item, updates the state when changed
placeholder="Item Name"
/>
<input
name="description"
value={newItem.description}
onChange={handleChange}
// Input field for the description of the new item, updates the state when changed
placeholder="Item Description"
/>
<button onClick={addItem}>Add Item</button>
{/* Button to add the new item */}
</div>
);
};
// Export the App component as the default export
export default App;
Step 3: Explanation of TypeScript Changes
Detailed Breakdown and Comments
State Initialization (
useState
):- We use
useState
to initialize and manage state. For example,const [items, setItems] = useState<Item[]>([]);
defines an array of items where each item conforms to theItem
interface.
- We use
Fetching Data with
useEffect
:- The
useEffect
hook fetches data from the server when the component is first rendered. Inside it, we callaxios.get()
to send a GET request, and we handle the response by setting theitems
state.
- The
Handling Form Changes (
handleChange
):- The
handleChange
function dynamically updates thenewItem
state when input values change, using thename
attribute of the input field as a key to update the respective property in the state.
- The
Adding a New Item (
addItem
):- When you click the “Add Item” button, the
addItem
function sends a POST request to add the new item to the server. After successfully adding it, we update the state by adding the new item to the existing list and clearing the input fields.
- When you click the “Add Item” button, the
Editing Items (
startEdit
,saveEdit
):- The
startEdit
function is called when you click “Edit”, and it sets theeditItem
state to the selected item. When you finish editing, thesaveEdit
function sends a PUT request to update the item on the server and updates the state to reflect the changes.
- The
Deleting Items (
deleteItem
):- The
deleteItem
function sends a DELETE request to remove an item from the server. After the item is deleted, it updates theitems
state by filtering out the deleted item.
- The
Conditional Rendering for Edit Mode:
- When an item is being edited, the input fields for editing are shown; otherwise, the item’s name and description are displayed. This is handled using conditional rendering in the JSX.
TypeScript-Specific Concepts:
Interfaces:
- The
Item
interface defines the shape of each item in the list. This ensures that when we work withitems
, TypeScript will enforce that each item has anid
,name
, anddescription
.
- The
Type Annotations:
- In
handleChange
, the parametere: ChangeEvent<HTMLInputElement>
ensures that the event type corresponds to changes on input elements, providing better autocompletion and error-checking.
- In
Generics in Axios:
axios.get<Item[]>
andaxios.post<Item>
use generics to inform TypeScript about the expected response type, ensuring that we get type safety when working with the response data.
Step 4: Running the TypeScript Project
Make sure your JSON server is running on port 5000:
npx json-server --watch db.json --port 5000
Start the React development server:
npm run dev
Summary of TypeScript Features:
- Static Typing: TypeScript ensures that data passed through your components conforms to the expected type, reducing runtime errors.
- Interfaces: You define
Item
and other types to structure your app’s data clearly. - Type Safety: Functions like
handleChange
andstartEdit
have explicit types for arguments and return values, improving code clarity.
Welcome to DevTechTutor.com, your ultimate resource for mastering web development and technology! Whether you're a beginner eager to dive into coding or an experienced developer looking to sharpen your skills, DevTechTutor.com is here to guide you every step of the way. Our mission is to make learning web development accessible, engaging, and effective.