05. State and useState Hook
Level: Beginner
- What state is and why components need it
- useState hook: creating and updating state
- State vs props difference
- Multiple state variables
- Form state for School Management System
- Common state mistakes to avoid
The Problem
Props flow down from parent. State is the component's own data. A student form needs to remember the student's name as they type. A modal needs to remember if it's open or closed. Props can't do this — only state can. Every interactive component needs state.
Props are read-only. State is data the component owns and can change.
What is State?
State is the component's memory. It remembers things between renders.
// Without state — no memory
function StudentCounter() {
let count = 0; // This resets every render
return (
<div>
<p>Clicked {count} times</p>
<button onClick={() => { count++; }}>Click</button>
</div>
);
}
// With state — remembers across renders
function StudentCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
);
}
Clicking the button with state updates the display. Without state, nothing changes.
useState Hook
Hooks are special functions that "hook into" React features. useState adds state to a component.
Basic Syntax
const [state, setState] = useState(initialValue);
state— current valuesetState— function to update itinitialValue— what it starts with
Example 1: Simple Counter
function StudentCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
Example 2: Form Input
function StudentSearch() {
const [searchName, setSearchName] = useState("");
const handleChange = (e) => {
setSearchName(e.target.value);
};
return (
<div>
<input
type="text"
placeholder="Search student..."
value={searchName}
onChange={handleChange}
/>
<p>Searching for: {searchName}</p>
</div>
);
}
Example 3: Toggle
function StudentDetail({ studentId }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? "Hide" : "Show"} Details
</button>
{isOpen && (
<div className="details">
<p>Student ID: {studentId}</p>
<p>Loading details...</p>
</div>
)}
</div>
);
}
State Best Practices
1. Multiple State Variables
function StudentForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [className, setClassName] = useState("10");
return (
<form>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<select
value={className}
onChange={(e) => setClassName(e.target.value)}
>
<option value="10">Class 10</option>
<option value="11">Class 11</option>
</select>
</form>
);
}
2. State with Objects
function StudentForm() {
const [formData, setFormData] = useState({
name: "",
email: "",
className: "10"
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
};
return (
<form>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<select
name="className"
value={formData.className}
onChange={handleChange}
>
<option value="10">Class 10</option>
<option value="11">Class 11</option>
</select>
</form>
);
}
3. State with Arrays
function StudentList() {
const [students, setStudents] = useState([
{ id: 1, name: "Ravi Kumar" },
{ id: 2, name: "Priya Sharma" }
]);
const addStudent = (name) => {
const newStudent = {
id: students.length + 1,
name: name
};
setStudents([...students, newStudent]);
};
const removeStudent = (id) => {
setStudents(students.filter(s => s.id !== id));
};
return (
<div>
<button onClick={() => addStudent("Arjun Reddy")}>Add Student</button>
<ul>
{students.map(student => (
<li key={student.id}>
{student.name}
<button onClick={() => removeStudent(student.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
State Updates — Immutability
React detects changes by comparing old and new objects. Always create new objects/arrays, don't modify existing ones.
// ❌ Wrong — directly modifying state
const [student, setStudent] = useState({ name: "Ravi", marks: 90 });
// This won't trigger re-render
student.marks = 95;
setStudent(student);
// ✓ Correct — create new object
const [student, setStudent] = useState({ name: "Ravi", marks: 90 });
setStudent({ ...student, marks: 95 });
// ❌ Wrong — directly modifying array
const [students, setStudents] = useState(["Ravi", "Priya"]);
students.push("Arjun");
setStudents(students);
// ✓ Correct — create new array
const [students, setStudents] = useState(["Ravi", "Priya"]);
setStudents([...students, "Arjun"]);
State Initialization
Simple Value
const [count, setCount] = useState(0);
const [isActive, setIsActive] = useState(true);
const [name, setName] = useState("Ravi Kumar");
From Props
function StudentForm({ initialName = "" }) {
const [name, setName] = useState(initialName);
return <input value={name} onChange={(e) => setName(e.target.value)} />;
}
From Function (Lazy Initialization)
Use if initial value requires computation:
function StudentList() {
const [students, setStudents] = useState(() => {
// Expensive computation
return loadStudentsFromDatabase();
});
return <div>Students: {students.length}</div>;
}
Common Patterns
Pattern 1: Async State (Loading/Error)
function StudentDetail({ studentId }) {
const [student, setStudent] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchStudent = async () => {
try {
setLoading(true);
const response = await fetch(`/api/students/${studentId}`);
const data = await response.json();
setStudent(data);
setError(null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <div>{student.name}</div>;
}
Pattern 2: Computed State
function StudentMarks() {
const [marks, setMarks] = useState([90, 85, 88]);
// Don't store computed value, calculate on render
const total = marks.reduce((sum, m) => sum + m, 0);
const average = total / marks.length;
return (
<div>
<p>Total: {total}</p>
<p>Average: {average.toFixed(2)}</p>
</div>
);
}
Updating State Based on Previous Value
Use function form of setState:
function StudentCounter() {
const [count, setCount] = useState(0);
const increment = () => {
// ✓ Correct — uses latest count
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>{count}</p>
<button onClick={() => increment()}>+</button>
</div>
);
}
This matters when state updates are queued:
function StudentCounter() {
const [count, setCount] = useState(0);
const handleTripleClick = () => {
// ✓ Correct — each uses the updated previous value
setCount(prevCount => prevCount + 1); // 0 -> 1
setCount(prevCount => prevCount + 1); // 1 -> 2
setCount(prevCount => prevCount + 1); // 2 -> 3
};
return <button onClick={handleTripleClick}>Triple Increment</button>;
}
Building Complete Form Example
function StudentRegistration() {
const [formData, setFormData] = useState({
name: "",
email: "",
className: "10",
parentName: "",
parentPhone: ""
});
const [submitted, setSubmitted] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("Submitting:", formData);
setSubmitted(true);
};
if (submitted) {
return (
<div className="success">
<h2>Registration Successful!</h2>
<p>Student: {formData.name}</p>
<button onClick={() => setSubmitted(false)}>Register Another</button>
</div>
);
}
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Student Name"
required
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
required
/>
<select
name="className"
value={formData.className}
onChange={handleChange}
>
<option value="10">Class 10</option>
<option value="11">Class 11</option>
<option value="12">Class 12</option>
</select>
<input
name="parentName"
value={formData.parentName}
onChange={handleChange}
placeholder="Parent Name"
/>
<input
name="parentPhone"
value={formData.parentPhone}
onChange={handleChange}
placeholder="Parent Phone"
/>
<button type="submit">Register</button>
</form>
);
}
Key Takeaways
- State = component memory
useStateadds state to components- State is immutable — create new objects
- Multiple state variables is fine
- Use function form for updates based on previous value
- Next: useEffect for side effects
- Modifying state directly —
state.name = "new"won't work. Use setter - Using state in conditionals — State updates are async, value might be stale
- Forgetting to create new objects — React won't detect changes if you modify directly
- Too much state — If state is complex, consider useReducer (advanced)
Keep state as close as possible to where it's used. If only StudentCard needs it, put state there, not in parent.
Use ChatGPT, Claude, or Copilot to go deeper on useState Hook. Try these prompts:
"Why must you create new objects instead of modifying state directly?""When would you use multiple useState vs. one object state?""What's the difference between state and props?""Quiz me on useState and state management"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.
Quick Definitions
- State and useState Hook - The main React concept explained in this lesson.
- Component - A reusable function that returns UI.
- Props/state/effects - Core React ideas used to pass data, remember data, and run side effects.
- Real project usage - How this appears in forms, dashboards, routes, and API-connected pages.
Common Mistakes
- Copying React code without understanding data flow
- Mutating arrays or objects directly instead of creating new values
- Forgetting keys, dependencies, loading states, or error states where needed
- Putting too much logic in one component
- Not testing the screen with realistic School Management System data
Practice Task
Create a small React example using State and useState Hook. Keep it focused on one School Management System screen.
Suggested practice:
- Build a small component or page for students, attendance, marks, or fees.
- Pass realistic data into the component.
- Add one success state and one empty/error state where relevant.
- Explain the data flow in your own words.
- Rebuild the same example once without looking at the article.
Quick Revision
| Question | Answer |
|---|---|
| What is the main idea? | Understand and apply State and useState Hook in React. |
| Where is it used? | Student dashboards, forms, tables, routes, and API-connected screens. |
| What should beginners focus on? | Clear components, predictable data flow, and small examples. |
| What is the best debugging habit? | Check props, state, render output, and browser console step by step. |
State and useState Hook is a React concept used to build clear, reusable, and predictable user interfaces. I would explain the problem it solves, show a small component example, and mention a common mistake beginners should avoid.
It is used in screens like student lists, admission forms, attendance dashboards, marks reports, routing pages, and API-connected admin panels.