Qaisar Manzoor 2025-12-20
React Front-End Development Lab: Student
Management System
Course: Web Technologies Lab Module: Front-End Development with React Level: BS Duration: 3 Hours
1. Project Overview
In this lab, you will build a complete Student Management System from scratch. This project is a Single Page
Application (SPA) that allows users to View, Add, Edit, and Delete student records.
Learning Objectives
By completing this project, you will demonstrate mastery of:
Component Structure: Breaking down a UI into reusable functional components.
React Hooks: Using useState for state management and useEffect for side effects (localStorage).
Props: Passing data and functions between parent and child components.
Routing: Using react-router-dom to create extensive Multi-View applications.
CRUD Operations: Implementing Create, Read, Update, and Delete logic in the front-end.
Features
1. Home Page: Dashboard with quick links.
2. Student List: Searchable and filterable list of all students.
3. Student Details: Individual view showing extended student information.
4. Add/Edit Forms: A reusable form to create new students or update existing ones.
5. Data Persistence: Data is saved to the browser's LocalStorage so it survives page reloads.
2. Environment Setup
We will use Vite, a modern build tool that is faster and more efficient than Create React App.
Prerequisites
[Link] (v14 or higher) installed.
VS Code installed.
Step 1: Initialize Project
Open your terminal (Command Prompt or PowerShell) and run the following commands:
# Create a new project called 'student-sys'
npm create vite@latest student-sys -- --template react
# Navigate into the folder
cd student-sys
1 / 14
2025-12-20
# Install dependencies
npm install
# Install React Router (Crucial for navigation)
npm install react-router-dom
Step 2: Open in Editor
Open the folder in VS Code:
code .
3. Folder Structure
Before writing code, we must organize our project. Delete the default contents of the src folder (except
[Link]) and create the following directory structure:
src/
├── components/ # Reusable UI pieces (Navbar, Cards)
│ ├── [Link]
│ └── [Link]
├── pages/ # Full page components used in Routes
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── data/ # Mock data service
│ └── [Link]
├── [Link] # Main Component & Routing Logic
├── [Link] # Global Styles
└── [Link] # Entry Point (pre-generated)
Task: Create these folders in your project manually now.
4. File-by-File Creation Guide
Follow this guide sequentially to build the application.
A. Mock Data Service
File: src/data/[Link] Purpose: Since we don't have a backend database, we need some initial data to
act as our starting point.
2 / 14
2025-12-20
export const initialStudents = [
{
id: 1,
name: "John Doe",
email: "[Link]@[Link]",
age: 20,
course: "Computer Science",
gpa: 3.8,
image: "[Link]
},
{
id: 2,
name: "Jane Smith",
email: "[Link]@[Link]",
age: 21,
course: "Information Technology",
gpa: 3.6,
image: "[Link]
name=Jane+Smith&background=6c5ce7&color=fff"
},
{
id: 3,
name: "Mike Johnson",
email: "[Link]@[Link]",
age: 22,
course: "Software Engineering",
gpa: 3.2,
image: "[Link]
name=Mike+Johnson&background=00b894&color=fff"
}
];
Key Concepts:
Exporting Data: Using export const allows us to import this array in [Link].
B. Global Styling
File: src/[Link] Purpose: Defines the look and feel of the application using CSS variables for
consistency.
/* Google Fonts Import */
@import url('[Link]
family=Inter:wght@400;500;600;700&display=swap');
:root {
--primary: #4f46e5;
--secondary: #64748b;
--background: #f1f5f9;
--surface: #ffffff;
3 / 14
2025-12-20
--text-main: #0f172a;
--text-secondary: #475569;
--border: #e2e8f0;
--danger: #ef4444;
--radius: 0.5rem;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', sans-serif; background-color: var(--background);
color: var(--text-main); line-height: 1.6; }
a { text-decoration: none; color: inherit; }
/* Utility Classes */
.container { max-width: 1200px; margin: 0 auto; padding: 0 1.5rem; }
.card { background: var(--surface); border-radius: var(--radius); border: 1px
solid var(--border); overflow: hidden; }
.text-center { text-align: center; }
.flex-between { display: flex; justify-content: space-between; align-items:
center; }
/* Buttons */
.btn { display: inline-flex; padding: 0.5rem 1rem; border-radius: var(--radius);
font-weight: 500; cursor: pointer; border: none; transition: 0.2s; gap: 0.5rem; }
.btn-primary { background: var(--primary); color: white; }
.btn-primary:hover { background: #4338ca; }
.btn-danger { background: var(--danger); color: white; }
.btn-ghost { background: transparent; color: var(--text-secondary); border: 1px
solid var(--border); }
.btn-ghost:hover { background: var(--background); }
/* Forms */
.form-group { margin-bottom: 1rem; }
.form-label { display: block; font-weight: 500; margin-bottom: 0.5rem; }
.form-input { width: 100%; padding: 0.625rem; border-radius: var(--radius);
border: 1px solid var(--border); }
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px,
1fr)); gap: 2rem; }
/* Navbar */
.navbar { background: var(--surface); border-bottom: 1px solid var(--border);
height: 4rem; display: flex; align-items: center; }
.nav-container { display: flex; justify-content: space-between; width: 100%; max-
width: 1200px; margin: 0 auto; padding: 0 1.5rem; }
.nav-links { display: flex; gap: 2rem; }
.nav-link { color: var(--text-secondary); font-weight: 500; }
.[Link] { color: var(--primary); }
C. Components
1. Navbar Component
4 / 14
2025-12-20
File: src/components/[Link] Purpose: A persistent header that allows navigation between pages.
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
const Navbar = () => {
const location = useLocation();
// Helper to highlight active link
const isActive = (path) => {
return [Link] === path ? 'nav-link active' : 'nav-link';
};
return (
<nav className="navbar">
<div className="nav-container">
<Link to="/" style={{ fontWeight: 700, fontSize: '1.5rem', color: 'var(--
primary)' }}>
UniManager
</Link>
<div className="nav-links">
<Link to="/" className={isActive('/')}>Home</Link>
<Link to="/students" className={isActive('/students')}>Students</Link>
<Link to="/add-student" className="btn btn-primary" style={{color:
'white'}}>+ Add Student</Link>
</div>
</div>
</nav>
);
};
export default Navbar;
Key Concepts:
Link: Replaces the standard <a> tag. Used for client-side navigation without page refresh.
useLocation Hook: Returns the current URL object. We use it to assign the 'active' class to the current
menu item.
2. StudentCard Component
File: src/components/[Link] Purpose: A reusable component to display a summary of a single
student.
import React from 'react';
import { Link } from 'react-router-dom';
const StudentCard = ({ student, onDelete }) => {
return (
<div className="card">
5 / 14
2025-12-20
<div style={{ height: '150px', background: '#f8fafc', display: 'flex',
justifyContent: 'center' }}>
<img src={[Link]} alt={[Link]} style={{ height: '100%',
objectFit: 'cover' }} />
</div>
<div style={{ padding: '1.5rem' }}>
<h3>{[Link]}</h3>
<p style={{ color: 'var(--text-secondary)', marginBottom: '1rem' }}>
{[Link]}</p>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<Link to={`/students/${[Link]}`} className="btn btn-ghost" style={{
flex: 1 }}>View</Link>
<Link to={`/edit-student/${[Link]}`} className="btn btn-ghost"
style={{ flex: 1 }}>Edit</Link>
<button onClick={() => onDelete([Link])} className="btn btn-danger">
</button>
</div>
</div>
</div>
);
};
export default StudentCard;
Key Concepts:
Props: Receives student (data object) and onDelete (function) from the parent.
Dynamic Links: to={'/students/' + [Link]} builds a unique URL for each student.
D. Pages
1. Home Page
File: src/pages/[Link] Purpose: Simple landing page.
import React from 'react';
import { Link } from 'react-router-dom';
const Home = () => {
return (
<div className="container text-center" style={{ padding: '4rem 0' }}>
<h1 style={{ fontSize: '3rem', color: 'var(--primary)', marginBottom: '1rem'
}}>Welcome to UniManager</h1>
<p style={{ fontSize: '1.25rem', color: 'var(--text-secondary)',
marginBottom: '2rem' }}>
Manage student records efficiently and securely.
</p>
<Link to="/students" className="btn btn-primary" style={{ padding: '0.75rem
2rem', fontSize: '1.1rem' }}>
6 / 14
2025-12-20
View All Students
</Link>
</div>
);
};
export default Home;
2. Student List Page
File: src/pages/[Link] Purpose: Displays the grid of students with search functionality.
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import StudentCard from '../components/StudentCard';
// Accepts 'students' data and 'onDelete' function as props from [Link]
const StudentList = ({ students, onDelete }) => {
const [searchTerm, setSearchTerm] = useState('');
// Search Logic
const filteredStudents = [Link](student =>
[Link]().includes([Link]())
);
return (
<div className="container" style={{ padding: '2rem 1.5rem' }}>
<div className="flex-between" style={{ marginBottom: '2rem' }}>
<h1>Student Directory</h1>
<input
type="text"
placeholder="Search students..."
className="form-input"
style={{ width: '300px' }}
value={searchTerm}
onChange={(e) => setSearchTerm([Link])}
/>
</div>
<div className="grid-3">
{[Link] > 0 ? (
[Link](student => (
<StudentCard
key={[Link]}
student={student}
onDelete={onDelete}
/>
))
) : (
<p>No students found.</p>
)}
7 / 14
2025-12-20
</div>
</div>
);
};
export default StudentList;
Key Concepts:
useState: Used to track the searchTerm input.
Filtering: We create a derived array filteredStudents based on the search term, rather than deleting
data.
Rendering Lists: Note the use of .map() to transform data arrays into UI components. A unique key
prop is required.
3. Student Details Page
File: src/pages/[Link] Purpose: Detailed view of a specific student found by ID.
import React from 'react';
import { useParams, Link } from 'react-router-dom';
const StudentDetails = ({ students }) => {
const { id } = useParams(); // Get ID from URL
const student = [Link](s => [Link] === parseInt(id));
if (!student) return <div className="container"><h2>Student not found</h2>
</div>;
return (
<div className="container" style={{ padding: '2rem' }}>
<Link to="/students" className="btn btn-ghost" style={{ marginBottom: '1rem'
}}>← Back</Link>
<div className="card" style={{ padding: '2rem' }}>
<div style={{ display: 'flex', gap: '2rem', alignItems: 'center' }}>
<img src={[Link]} alt={[Link]} style={{ width: '150px',
borderRadius: '50%' }} />
<div>
<h1>{[Link]}</h1>
<p style={{ fontSize: '1.25rem', color: 'var(--primary)' }}>
{[Link]}</p>
</div>
</div>
<hr style={{ margin: '2rem 0', border: 'none', borderTop: '1px solid var(-
-border)' }} />
<div className="grid-3">
<div><label className="form-label">Email</label><p>{[Link]}</p>
</div>
<div><label className="form-label">Age</label><p>{[Link]}</p></div>
<div><label className="form-label">GPA</label><p>{[Link]}</p></div>
</div>
8 / 14
2025-12-20
</div>
</div>
);
};
export default StudentDetails;
Key Concepts:
useParams Hook: Extracts parameters from the URL. If the URL is /students/5, useParams() returns
{ id: '5' }.
[Link](): Locates the specific student object from the main array.
4. Add / Edit Form
File: src/pages/[Link] Purpose: A single valid form to handle creating NEW students AND
updating EXISTING ones.
import React, { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
const AddEditStudent = ({ students, onAdd, onUpdate }) => {
const { id } = useParams(); // If ID exists, we are in EDIT mode
const navigate = useNavigate();
const [formData, setFormData] = useState({
name: '', email: '', course: '', age: '', gpa: '', image: ''
});
// Load data if editing
useEffect(() => {
if (id) {
const student = [Link](s => [Link] === parseInt(id));
if (student) setFormData(student);
}
}, [id, students]);
const handleSubmit = (e) => {
[Link]();
if (id) {
// Edit Mode
onUpdate({ ...formData, id: parseInt(id) });
} else {
// Add Mode: Generate generic ID + Image
onAdd({
...formData,
id: [Link](),
image: `[Link]
name=${[Link]}&background=random`
});
}
9 / 14
2025-12-20
navigate('/students'); // Go back to list
};
return (
<div className="container" style={{ padding: '2rem', maxWidth: '600px' }}>
<h1>{id ? 'Edit Student' : 'Add New Student'}</h1>
<form onSubmit={handleSubmit} className="card" style={{ padding: '2rem',
marginTop: '1rem' }}>
<div className="form-group">
<label className="form-label">Name</label>
<input className="form-input" required
value={[Link]}
onChange={e => setFormData({...formData, name: [Link]})}
/>
</div>
<div className="form-group">
<label className="form-label">Email</label>
<input className="form-input" type="email" required
value={[Link]}
onChange={e => setFormData({...formData, email: [Link]})}
/>
</div>
<div className="form-group">
<label className="form-label">Course</label>
<select className="form-input" required
value={[Link]}
onChange={e => setFormData({...formData, course: [Link]})}
>
<option value="">Select Course</option>
<option>Computer Science</option>
<option>Software Engineering</option>
<option>Information Technology</option>
</select>
</div>
<button className="btn btn-primary" style={{ width: '100%' }}>Save
Student</button>
</form>
</div>
);
};
export default AddEditStudent;
Key Concepts:
Controlled Inputs: The inputs' value comes from state, and onChange updates state.
Reusable Logic: Checking if (id) allows us to reuse the same form UI for two logic paths (Create vs
Update).
E. App Core (The "Brain")
10 / 14
2025-12-20
Main App Component
File: src/[Link] Purpose: Holds the State (the student database) and manages Routing.
import { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar';
import Home from './pages/Home';
import StudentList from './pages/StudentList';
import StudentDetails from './pages/StudentDetails';
import AddEditStudent from './pages/AddEditStudent';
import { initialStudents } from './data/students';
function App() {
// 1. Initialize State (Try localStorage first, else use mock data)
const [students, setStudents] = useState(() => {
const saved = [Link]('students');
return saved ? [Link](saved) : initialStudents;
});
// 2. Save to LocalStorage whenever 'students' changes
useEffect(() => {
[Link]('students', [Link](students));
}, [students]);
// 3. CRUD Functions
const addStudent = (student) => {
setStudents([...students, student]);
};
const updateStudent = (updatedStudent) => {
setStudents([Link](s => ([Link] === [Link] ? updatedStudent :
s)));
};
const deleteStudent = (id) => {
setStudents([Link](s => [Link] !== id));
};
return (
<Router>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
{/* Pass data and actions as props */}
<Route path="/students" element={
<StudentList students={students} onDelete={deleteStudent} />
} />
<Route path="/students/:id" element={
<StudentDetails students={students} />
11 / 14
Qaisar Manzoor 2025-12-20
} />
<Route path="/add-student" element={
<AddEditStudent students={students} onAdd={addStudent} />
} />
<Route path="/edit-student/:id" element={
<AddEditStudent students={students} onUpdate={updateStudent} />
} />
</Routes>
</Router>
);
}
export default App;
Key Concepts:
Lifting State Up: The students state lives in App so it can be shared with ALL pages.
Persisting State: The useState lazy initializer and useEffect hook work together to sync data with
localStorage.
Entry Point
File: src/[Link] Purpose: Mounts the React application to the DOM.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './[Link]'
import './[Link]'
[Link]([Link]('root')).render(
<[Link]>
<App />
</[Link]>,
)
5. CRUD Logic Explanation
Create (Add)
Logic: setStudents([...students, newStudent])
Explanation: We rely on immutability. We create a copy of the existing array (...students), add the
new item, and replace the old state.
Read (List/Details)
Logic: Pass students array as a prop.
12 / 14
2025-12-20
Explanation: Components render the data passed to them. StudentList maps over the array.
StudentDetails searches the array specifically for one ID.
Update (Edit)
Logic: [Link](s => [Link] === [Link] ? updated : s)
Explanation: We iterate through the array. If the ID matches the one we edited, we swap it with the
new object. If not, we keep the original. This creates a new updated array.
Delete (Remove)
Logic: [Link](s => [Link] !== id)
Explanation: We create a new array containing only the students whose ID does not match the deletion
ID.
6. How to Run the Project
1. Ensure you have saved all files.
2. In the terminal, ensure you are in the project folder.
3. Run:
npm run dev
(Note: If you used 'create-react-app', the command is npm start. If you used Vite as instructed, it is npm
run dev).
4. Opening the link shown (usually [Link] will launch your Student Management
System.
7. Common Mistakes & Fixes
1. "Switch/Route not found":
Fix: Ensure you imported Routes and Route from react-router-dom and wrapped them in
<Router>.
2. Form input not typing:
Fix: You created a "controlled component" (assigned value={state}) but forgot the onChange
handler to update that state.
3. Page blank on refresh:
Fix: Ensure localStorage logic handles empty or null data correctly (e.g., saved ?
[Link](saved) : []).
4. Images broken:
Fix: Ensure the UI Avatars URL is correctly formatted with backticks ` ` like: `[Link]
[Link]/api/?name=${name}`.
8. Student Tasks (Practice)
13 / 14
2025-12-20
Once you have the app running, try these extensions:
1. Add a Field: Add a "Phone Number" field to the students data, the AddEditStudent form, and the
StudentDetails page.
2. Validation: Add a check in AddEditStudent to prevent users from entering a GPA higher than 4.0.
3. Delete Confirmation: Add [Link]("Are you sure?") inside the delete function before
removing a student.
14 / 14