Master React state management with useState hook, understand props vs state, implement controlled components, and learn state management patterns for building dynamic applications.
State and props are the two fundamental concepts that make React components dynamic and interactive. While props allow data to flow down from parent to child components, state enables components to remember and update information over time.
Think of props as arguments passed to a function - they provide the input data your component needs to render correctly. State, on the other hand, is like the component's memory - it remembers values that can change over time, like user input, toggle states, or data fetched from APIs.
Understanding the relationship between state and props is crucial for building React applications. State changes trigger re-renders, props make components reusable, and together they create the reactive nature that makes React applications feel alive and responsive to user interactions.
In this comprehensive lesson, you'll master state management with the useState hook, understand when to use state versus props, learn controlled component patterns, and build interactive applications that respond dynamically to user input and changing data.
State is data that can change over time within a component. React provides the useState hook to add state to functional components, making them dynamic and interactive.
What is State?
State represents values that can change during the component's lifecycle:
The useState Hook:
1import React, { useState } from 'react';
2
3function Counter() {
4 // useState returns [currentValue, setterFunction]
5 const [count, setCount] = useState(0); // 0 is the initial value
6
7 const increment = () => {
8 setCount(count + 1); // Updates state and triggers re-render
9 };
10
11 return (
12 <div>
13 <p>Count: {count}</p>
14 <button onClick={increment}>Increment</button>
15 </div>
16 );
17}useState Syntax and Patterns:
Basic Usage:
1const [state, setState] = useState(initialValue);Multiple State Variables:
1function UserProfile() {
2 const [name, setName] = useState('');
3 const [email, setEmail] = useState('');
4 const [age, setAge] = useState(0);
5 const [isLoggedIn, setIsLoggedIn] = useState(false);
6
7 return (
8 <div>
9 <p>Name: {name}</p>
10 <p>Email: {email}</p>
11 <p>Age: {age}</p>
12 <p>Status: {isLoggedIn ? 'Logged In' : 'Logged Out'}</p>
13 </div>
14 );
15}Object State:
1function ContactForm() {
2 const [formData, setFormData] = useState({
3 name: '',
4 email: '',
5 message: ''
6 });
7
8 const updateField = (field, value) => {
9 setFormData(prev => ({
10 ...prev, // Spread previous state
11 [field]: value // Update specific field
12 }));
13 };
14
15 return (
16 <form>
17 <input
18 type="text"
19 value={formData.name}
20 onChange={(e) => updateField('name', e.target.value)}
21 placeholder="Name"
22 />
23 <input
24 type="email"
25 value={formData.email}
26 onChange={(e) => updateField('email', e.target.value)}
27 placeholder="Email"
28 />
29 <textarea
30 value={formData.message}
31 onChange={(e) => updateField('message', e.target.value)}
32 placeholder="Message"
33 />
34 </form>
35 );
36}Array State:
1function TodoList() {
2 const [todos, setTodos] = useState([]);
3 const [inputText, setInputText] = useState('');
4
5 const addTodo = () => {
6 if (inputText.trim()) {
7 setTodos(prev => [
8 ...prev,
9 {
10 id: Date.now(),
11 text: inputText,
12 completed: false
13 }
14 ]);
15 setInputText(''); // Clear input
16 }
17 };
18
19 const toggleTodo = (id) => {
20 setTodos(prev =>
21 prev.map(todo =>
22 todo.id === id
23 ? { ...todo, completed: !todo.completed }
24 : todo
25 )
26 );
27 };
28
29 const removeTodo = (id) => {
30 setTodos(prev => prev.filter(todo => todo.id !== id));
31 };
32
33 return (
34 <div>
35 <input
36 type="text"
37 value={inputText}
38 onChange={(e) => setInputText(e.target.value)}
39 onKeyPress={(e) => e.key === 'Enter' && addTodo()}
40 placeholder="Add a todo..."
41 />
42 <button onClick={addTodo}>Add Todo</button>
43
44 <ul>
45 {todos.map(todo => (
46 <li key={todo.id}>
47 <input
48 type="checkbox"
49 checked={todo.completed}
50 onChange={() => toggleTodo(todo.id)}
51 />
52 <span style={'{'{{ textDecoration: todo.completed ? 'line-through' : 'none' }}'}}>
53 {todo.text}
54 </span>
55 <button onClick={() => removeTodo(todo.id)}>Delete</button>
56 </li>
57 ))}
58 </ul>
59 </div>
60 );
61}Functional State Updates:
When the new state depends on the previous state, use a function:
1function Counter() {
2 const [count, setCount] = useState(0);
3
4 // ❌ Don't do this - can cause bugs with rapid clicks
5 const increment = () => {
6 setCount(count + 1);
7 setCount(count + 1); // This won't work as expected!
8 };
9
10 // ✅ Do this - use functional updates
11 const increment = () => {
12 setCount(prev => prev + 1);
13 setCount(prev => prev + 1); // This works correctly!
14 };
15
16 return (
17 <div>
18 <p>Count: {count}</p>
19 <button onClick={increment}>Increment by 2</button>
20 </div>
21 );
22}State Update Patterns:
Immediate Updates:
1const handleClick = () => {
2 setCount(5); // Direct value
3};Computed Updates:
1const handleCalculation = () => {
2 const result = someComplexCalculation(data);
3 setResult(result);
4};Conditional Updates:
1const handleToggle = () => {
2 setIsVisible(prev => !prev); // Toggle boolean
3};
4
5const handleIncrement = () => {
6 setCount(prev => prev < 10 ? prev + 1 : prev); // Conditional increment
7};Common useState Patterns:
Loading States:
1function DataFetcher() {
2 const [data, setData] = useState(null);
3 const [loading, setLoading] = useState(false);
4 const [error, setError] = useState(null);
5
6 const fetchData = async () => {
7 setLoading(true);
8 setError(null);
9
10 try {
11 const response = await fetch('/api/data');
12 const result = await response.json();
13 setData(result);
14 } catch (err) {
15 setError(err.message);
16 } finally {
17 setLoading(false);
18 }
19 };
20
21 return (
22 <div>
23 {loading && <p>Loading...</p>}
24 {error && <p>Error: {error}</p>}
25 {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
26 <button onClick={fetchData}>Fetch Data</button>
27 </div>
28 );
29}Form Validation States:
1function ValidatedForm() {
2 const [email, setEmail] = useState('');
3 const [errors, setErrors] = useState({});
4 const [touched, setTouched] = useState({});
5
6 const validateEmail = (value) => {
7 if (!value) return 'Email is required';
8 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
9 return 'Please enter a valid email';
10 }
11 return '';
12 };
13
14 const handleEmailChange = (e) => {
15 const value = e.target.value;
16 setEmail(value);
17
18 if (touched.email) {
19 const error = validateEmail(value);
20 setErrors(prev => ({ ...prev, email: error }));
21 }
22 };
23
24 const handleEmailBlur = () => {
25 setTouched(prev => ({ ...prev, email: true }));
26 const error = validateEmail(email);
27 setErrors(prev => ({ ...prev, email: error }));
28 };
29
30 return (
31 <div>
32 <input
33 type="email"
34 value={email}
35 onChange={handleEmailChange}
36 onBlur={handleEmailBlur}
37 className={errors.email ? 'error' : ''}
38 />
39 {errors.email && <span className="error-text">{errors.email}</span>}
40 </div>
41 );
42}Key useState Rules:
State is what makes React components dynamic and interactive, turning static UI descriptions into living, breathing user interfaces.
Understanding when to use props versus state is fundamental to building well-architected React applications. Each serves a different purpose in the component hierarchy and data flow.
Props: External Data Flow
Props are data passed from parent to child components. They are:
1// Parent component controls the data
2function App() {
3 const user = {
4 name: 'Alice Johnson',
5 email: '[email protected]',
6 role: 'Developer'
7 };
8
9 return (
10 <div>
11 <UserCard user={user} showEmail={true} />
12 <UserCard user={user} showEmail={false} />
13 </div>
14 );
15}
16
17// Child component receives and displays props
18function UserCard({ user, showEmail }) {
19 // ❌ Cannot modify props
20 // user.name = 'Bob'; // This would be an error!
21
22 return (
23 <div className="user-card">
24 <h3>{user.name}</h3>
25 <p>Role: {user.role}</p>
26 {showEmail && <p>Email: {user.email}</p>}
27 </div>
28 );
29}State: Internal Component Memory
State is data that belongs to and is controlled by a component:
1function Counter() {
2 // State belongs to this component
3 const [count, setCount] = useState(0);
4 const [isEven, setIsEven] = useState(true);
5
6 const increment = () => {
7 setCount(prev => {
8 const newCount = prev + 1;
9 setIsEven(newCount % 2 === 0);
10 return newCount;
11 });
12 };
13
14 return (
15 <div>
16 <p>Count: {count} ({isEven ? 'Even' : 'Odd'})</p>
17 <button onClick={increment}>Increment</button>
18 </div>
19 );
20}Decision Matrix: Props vs State
Use Props When:
1// Props make components reusable and configurable
2function Button({ text, color, size, onClick, disabled }) {
3 return (
4 <button
5 className={`btn btn-${color} btn-${size}`}
6 onClick={onClick}
7 disabled={disabled}
8 >
9 {text}
10 </button>
11 );
12}
13
14// Same component, different configurations
15<Button text="Save" color="primary" size="large" onClick={handleSave} />
16<Button text="Cancel" color="secondary" size="small" onClick={handleCancel} />
17<Button text="Delete" color="danger" size="medium" onClick={handleDelete} />Use State When:
1function SearchBox({ onSearch, placeholder }) {
2 // State for internal component behavior
3 const [query, setQuery] = useState('');
4 const [isExpanded, setIsExpanded] = useState(false);
5
6 const handleSubmit = (e) => {
7 e.preventDefault();
8 onSearch(query); // Pass data up via props callback
9 };
10
11 return (
12 <form onSubmit={handleSubmit}>
13 <div className={isExpanded ? 'search-expanded' : 'search-collapsed'}>
14 <input
15 type="text"
16 value={query} // Controlled by state
17 onChange={(e) => setQuery(e.target.value)}
18 placeholder={placeholder} // Comes from props
19 onFocus={() => setIsExpanded(true)}
20 onBlur={() => setIsExpanded(false)}
21 />
22 <button type="submit">Search</button>
23 </div>
24 </form>
25 );
26}Data Flow Patterns:
Props Down, Events Up:
1function ShoppingCart({ items }) {
2 const [cartItems, setCartItems] = useState(items);
3
4 const updateQuantity = (itemId, newQuantity) => {
5 setCartItems(prev =>
6 prev.map(item =>
7 item.id === itemId
8 ? { ...item, quantity: newQuantity }
9 : item
10 )
11 );
12 };
13
14 const removeItem = (itemId) => {
15 setCartItems(prev => prev.filter(item => item.id !== itemId));
16 };
17
18 return (
19 <div>
20 {cartItems.map(item => (
21 <CartItem
22 key={item.id}
23 item={item} // Props down
24 onUpdateQuantity={updateQuantity} // Events up
25 onRemove={removeItem} // Events up
26 />
27 ))}
28 </div>
29 );
30}
31
32function CartItem({ item, onUpdateQuantity, onRemove }) {
33 return (
34 <div className="cart-item">
35 <h4>{item.name}</h4>
36 <p>Price: ${item.price}</p>
37 <input
38 type="number"
39 value={item.quantity}
40 onChange={(e) => onUpdateQuantity(item.id, parseInt(e.target.value))}
41 min="1"
42 />
43 <button onClick={() => onRemove(item.id)}>Remove</button>
44 </div>
45 );
46}Lifting State Up:
When multiple components need to share state, lift it up to their common parent:
1function TemperatureConverter() {
2 const [celsius, setCelsius] = useState('');
3
4 const handleCelsiusChange = (value) => {
5 setCelsius(value);
6 };
7
8 const handleFahrenheitChange = (value) => {
9 setCelsius(value === '' ? '' : ((parseFloat(value) - 32) * 5) / 9);
10 };
11
12 const fahrenheit = celsius === '' ? '' : (parseFloat(celsius) * 9) / 5 + 32;
13
14 return (
15 <div>
16 <TemperatureInput
17 scale="celsius"
18 temperature={celsius}
19 onTemperatureChange={handleCelsiusChange}
20 />
21 <TemperatureInput
22 scale="fahrenheit"
23 temperature={fahrenheit}
24 onTemperatureChange={handleFahrenheitChange}
25 />
26 </div>
27 );
28}
29
30function TemperatureInput({ scale, temperature, onTemperatureChange }) {
31 const scaleNames = {
32 celsius: 'Celsius',
33 fahrenheit: 'Fahrenheit'
34 };
35
36 return (
37 <fieldset>
38 <legend>Enter temperature in {scaleNames[scale]}:</legend>
39 <input
40 value={temperature}
41 onChange={(e) => onTemperatureChange(e.target.value)}
42 />
43 </fieldset>
44 );
45}State Structure Best Practices:
Keep State Flat:
1// ❌ Deeply nested state is hard to update
2const [user, setUser] = useState({
3 profile: {
4 personal: {
5 name: '',
6 address: {
7 street: '',
8 city: ''
9 }
10 }
11 }
12});
13
14// ✅ Flat state is easier to manage
15const [userName, setUserName] = useState('');
16const [userStreet, setUserStreet] = useState('');
17const [userCity, setUserCity] = useState('');Group Related State:
1// ❌ Too many separate state variables
2const [firstName, setFirstName] = useState('');
3const [lastName, setLastName] = useState('');
4const [email, setEmail] = useState('');
5const [phone, setPhone] = useState('');
6
7// ✅ Group related data
8const [contactInfo, setContactInfo] = useState({
9 firstName: '',
10 lastName: '',
11 email: '',
12 phone: ''
13});Derived State vs Stored State:
1function UserList({ users }) {
2 const [searchTerm, setSearchTerm] = useState('');
3
4 // ✅ Derive filtered users - don't store in state
5 const filteredUsers = users.filter(user =>
6 user.name.toLowerCase().includes(searchTerm.toLowerCase())
7 );
8
9 // ✅ Derive count - don't store in state
10 const userCount = filteredUsers.length;
11
12 return (
13 <div>
14 <input
15 type="text"
16 value={searchTerm}
17 onChange={(e) => setSearchTerm(e.target.value)}
18 placeholder="Search users..."
19 />
20 <p>Found {userCount} users</p>
21 {filteredUsers.map(user => (
22 <UserCard key={user.id} user={user} />
23 ))}
24 </div>
25 );
26}Common Anti-Patterns:
❌ Don't Copy Props to State (Usually):
1// This breaks when parent updates props
2function UserProfile({ user }) {
3 const [userName, setUserName] = useState(user.name); // ❌ Anti-pattern
4
5 return <div>{userName}</div>;
6}❌ Don't Mutate State Directly:
1const [items, setItems] = useState([]);
2
3const addItem = (item) => {
4 items.push(item); // ❌ Direct mutation
5 setItems(items); // Won't trigger re-render
6};✅ Use Immutable Updates:
1const addItem = (item) => {
2 setItems(prev => [...prev, item]); // ✅ Immutable update
3};Understanding the distinction between props and state is crucial for building maintainable React applications with clear data flow and component responsibilities.
Controlled components are form elements whose values are controlled by React state. This pattern gives you complete control over form behavior, validation, and user interactions, making forms predictable and powerful.
Controlled vs Uncontrolled Components:
Uncontrolled Components (DOM controls the value):
1function UncontrolledForm() {
2 const nameRef = useRef();
3
4 const handleSubmit = (e) => {
5 e.preventDefault();
6 console.log(nameRef.current.value); // Reading from DOM
7 };
8
9 return (
10 <form onSubmit={handleSubmit}>
11 <input type="text" ref={nameRef} defaultValue="Initial value" />
12 <button type="submit">Submit</button>
13 </form>
14 );
15}Controlled Components (React state controls the value):
1function ControlledForm() {
2 const [name, setName] = useState('Initial value');
3
4 const handleSubmit = (e) => {
5 e.preventDefault();
6 console.log(name); // Reading from state
7 };
8
9 return (
10 <form onSubmit={handleSubmit}>
11 <input
12 type="text"
13 value={name} // Value comes from state
14 onChange={(e) => setName(e.target.value)} // Updates state
15 />
16 <button type="submit">Submit</button>
17 </form>
18 );
19}Basic Controlled Component Patterns:
Text Input:
1function NameInput() {
2 const [name, setName] = useState('');
3
4 return (
5 <div>
6 <label htmlFor="name">Name:</label>
7 <input
8 id="name"
9 type="text"
10 value={name}
11 onChange={(e) => setName(e.target.value)}
12 placeholder="Enter your name"
13 />
14 <p>Hello, {name || 'Guest'}!</p>
15 </div>
16 );
17}Textarea:
1function MessageInput() {
2 const [message, setMessage] = useState('');
3
4 return (
5 <div>
6 <label htmlFor="message">Message:</label>
7 <textarea
8 id="message"
9 value={message}
10 onChange={(e) => setMessage(e.target.value)}
11 placeholder="Type your message..."
12 rows={4}
13 />
14 <p>Character count: {message.length}</p>
15 </div>
16 );
17}Select Dropdown:
1function CountrySelector() {
2 const [country, setCountry] = useState('');
3
4 const countries = [
5 { code: 'us', name: 'United States' },
6 { code: 'ca', name: 'Canada' },
7 { code: 'uk', name: 'United Kingdom' },
8 { code: 'au', name: 'Australia' }
9 ];
10
11 return (
12 <div>
13 <label htmlFor="country">Country:</label>
14 <select
15 id="country"
16 value={country}
17 onChange={(e) => setCountry(e.target.value)}
18 >
19 <option value="">Select a country</option>
20 {countries.map(c => (
21 <option key={c.code} value={c.code}>
22 {c.name}
23 </option>
24 ))}
25 </select>
26 {country && <p>You selected: {country}</p>}
27 </div>
28 );
29}Checkbox:
1function TermsCheckbox() {
2 const [agreedToTerms, setAgreedToTerms] = useState(false);
3
4 return (
5 <div>
6 <label>
7 <input
8 type="checkbox"
9 checked={agreedToTerms}
10 onChange={(e) => setAgreedToTerms(e.target.checked)}
11 />
12 I agree to the terms and conditions
13 </label>
14 <p>Status: {agreedToTerms ? 'Agreed' : 'Not agreed'}</p>
15 </div>
16 );
17}Radio Buttons:
1function SizeSelector() {
2 const [size, setSize] = useState('');
3
4 const sizes = ['small', 'medium', 'large'];
5
6 return (
7 <div>
8 <fieldset>
9 <legend>Select size:</legend>
10 {sizes.map(s => (
11 <label key={s}>
12 <input
13 type="radio"
14 name="size"
15 value={s}
16 checked={size === s}
17 onChange={(e) => setSize(e.target.value)}
18 />
19 {s.charAt(0).toUpperCase() + s.slice(1)}
20 </label>
21 ))}
22 </fieldset>
23 {size && <p>Selected size: {size}</p>}
24 </div>
25 );
26}Complex Form with Multiple Fields:
1function UserRegistrationForm() {
2 const [formData, setFormData] = useState({
3 username: '',
4 email: '',
5 password: '',
6 confirmPassword: '',
7 age: '',
8 gender: '',
9 country: '',
10 newsletter: false,
11 terms: false
12 });
13
14 const [errors, setErrors] = useState({});
15 const [isSubmitting, setIsSubmitting] = useState(false);
16
17 // Generic handler for all form fields
18 const handleChange = (e) => {
19 const { name, value, type, checked } = e.target;
20 const fieldValue = type === 'checkbox' ? checked : value;
21
22 setFormData(prev => ({
23 ...prev,
24 [name]: fieldValue
25 }));
26
27 // Clear error when user starts typing
28 if (errors[name]) {
29 setErrors(prev => ({
30 ...prev,
31 [name]: ''
32 }));
33 }
34 };
35
36 // Validation function
37 const validateForm = () => {
38 const newErrors = {};
39
40 if (!formData.username.trim()) {
41 newErrors.username = 'Username is required';
42 } else if (formData.username.length < 3) {
43 newErrors.username = 'Username must be at least 3 characters';
44 }
45
46 if (!formData.email.trim()) {
47 newErrors.email = 'Email is required';
48 } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
49 newErrors.email = 'Please enter a valid email';
50 }
51
52 if (!formData.password) {
53 newErrors.password = 'Password is required';
54 } else if (formData.password.length < 8) {
55 newErrors.password = 'Password must be at least 8 characters';
56 }
57
58 if (formData.password !== formData.confirmPassword) {
59 newErrors.confirmPassword = 'Passwords do not match';
60 }
61
62 if (!formData.age) {
63 newErrors.age = 'Age is required';
64 } else if (formData.age < 13) {
65 newErrors.age = 'You must be at least 13 years old';
66 }
67
68 if (!formData.terms) {
69 newErrors.terms = 'You must accept the terms and conditions';
70 }
71
72 return newErrors;
73 };
74
75 const handleSubmit = async (e) => {
76 e.preventDefault();
77
78 const formErrors = validateForm();
79
80 if (Object.keys(formErrors).length > 0) {
81 setErrors(formErrors);
82 return;
83 }
84
85 setIsSubmitting(true);
86
87 try {
88 // Simulate API call
89 await new Promise(resolve => setTimeout(resolve, 2000));
90
91 alert('Registration successful!');
92
93 // Reset form
94 setFormData({
95 username: '',
96 email: '',
97 password: '',
98 confirmPassword: '',
99 age: '',
100 gender: '',
101 country: '',
102 newsletter: false,
103 terms: false
104 });
105 } catch (error) {
106 setErrors({ submit: 'Registration failed. Please try again.' });
107 } finally {
108 setIsSubmitting(false);
109 }
110 };
111
112 return (
113 <form onSubmit={handleSubmit} className="registration-form">
114 <h2>User Registration</h2>
115
116 <div className="form-group">
117 <label htmlFor="username">Username *</label>
118 <input
119 type="text"
120 id="username"
121 name="username"
122 value={formData.username}
123 onChange={handleChange}
124 className={errors.username ? 'error' : ''}
125 />
126 {errors.username && <span className="error-text">{errors.username}</span>}
127 </div>
128
129 <div className="form-group">
130 <label htmlFor="email">Email *</label>
131 <input
132 type="email"
133 id="email"
134 name="email"
135 value={formData.email}
136 onChange={handleChange}
137 className={errors.email ? 'error' : ''}
138 />
139 {errors.email && <span className="error-text">{errors.email}</span>}
140 </div>
141
142 <div className="form-group">
143 <label htmlFor="password">Password *</label>
144 <input
145 type="password"
146 id="password"
147 name="password"
148 value={formData.password}
149 onChange={handleChange}
150 className={errors.password ? 'error' : ''}
151 />
152 {errors.password && <span className="error-text">{errors.password}</span>}
153 </div>
154
155 <div className="form-group">
156 <label htmlFor="confirmPassword">Confirm Password *</label>
157 <input
158 type="password"
159 id="confirmPassword"
160 name="confirmPassword"
161 value={formData.confirmPassword}
162 onChange={handleChange}
163 className={errors.confirmPassword ? 'error' : ''}
164 />
165 {errors.confirmPassword && <span className="error-text">{errors.confirmPassword}</span>}
166 </div>
167
168 <div className="form-group">
169 <label htmlFor="age">Age *</label>
170 <input
171 type="number"
172 id="age"
173 name="age"
174 value={formData.age}
175 onChange={handleChange}
176 min="1"
177 max="120"
178 className={errors.age ? 'error' : ''}
179 />
180 {errors.age && <span className="error-text">{errors.age}</span>}
181 </div>
182
183 <div className="form-group">
184 <label htmlFor="gender">Gender</label>
185 <select
186 id="gender"
187 name="gender"
188 value={formData.gender}
189 onChange={handleChange}
190 >
191 <option value="">Select gender</option>
192 <option value="male">Male</option>
193 <option value="female">Female</option>
194 <option value="other">Other</option>
195 <option value="prefer-not-to-say">Prefer not to say</option>
196 </select>
197 </div>
198
199 <div className="form-group">
200 <label htmlFor="country">Country</label>
201 <select
202 id="country"
203 name="country"
204 value={formData.country}
205 onChange={handleChange}
206 >
207 <option value="">Select country</option>
208 <option value="us">United States</option>
209 <option value="ca">Canada</option>
210 <option value="uk">United Kingdom</option>
211 <option value="au">Australia</option>
212 </select>
213 </div>
214
215 <div className="form-group">
216 <label>
217 <input
218 type="checkbox"
219 name="newsletter"
220 checked={formData.newsletter}
221 onChange={handleChange}
222 />
223 Subscribe to newsletter
224 </label>
225 </div>
226
227 <div className="form-group">
228 <label>
229 <input
230 type="checkbox"
231 name="terms"
232 checked={formData.terms}
233 onChange={handleChange}
234 />
235 I accept the terms and conditions *
236 </label>
237 {errors.terms && <span className="error-text">{errors.terms}</span>}
238 </div>
239
240 {errors.submit && <div className="error-text">{errors.submit}</div>}
241
242 <button
243 type="submit"
244 disabled={isSubmitting}
245 className="submit-button"
246 >
247 {isSubmitting ? 'Registering...' : 'Register'}
248 </button>
249 </form>
250 );
251}Advanced Form Patterns:
Real-time Validation:
1function ValidatedInput({ label, name, value, onChange, validationRules }) {
2 const [error, setError] = useState('');
3 const [touched, setTouched] = useState(false);
4
5 const validate = (inputValue) => {
6 for (const rule of validationRules) {
7 const errorMessage = rule(inputValue);
8 if (errorMessage) return errorMessage;
9 }
10 return '';
11 };
12
13 const handleChange = (e) => {
14 const newValue = e.target.value;
15 onChange(name, newValue);
16
17 if (touched) {
18 setError(validate(newValue));
19 }
20 };
21
22 const handleBlur = () => {
23 setTouched(true);
24 setError(validate(value));
25 };
26
27 return (
28 <div className="form-group">
29 <label>{label}</label>
30 <input
31 type="text"
32 value={value}
33 onChange={handleChange}
34 onBlur={handleBlur}
35 className={error ? 'error' : ''}
36 />
37 {error && <span className="error-text">{error}</span>}
38 </div>
39 );
40}
41
42// Usage with validation rules
43const emailRules = [
44 (value) => !value ? 'Email is required' : '',
45 (value) => !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? 'Invalid email format' : ''
46];Benefits of Controlled Components:
Controlled components give you complete control over form behavior, making complex form interactions and validations straightforward to implement.
As React applications grow, managing state effectively becomes crucial. Understanding state management patterns helps you build scalable, maintainable applications with clear data flow and predictable behavior.
Local vs Global State:
Local State:
State that belongs to a specific component and doesn't need to be shared:
1function SearchBox() {
2 // Local state - only this component needs it
3 const [query, setQuery] = useState('');
4 const [isExpanded, setIsExpanded] = useState(false);
5 const [suggestions, setSuggestions] = useState([]);
6
7 // This state doesn't need to be shared with other components
8 return (
9 <div className={isExpanded ? 'search-expanded' : 'search-collapsed'}>
10 <input
11 value={query}
12 onChange={(e) => setQuery(e.target.value)}
13 onFocus={() => setIsExpanded(true)}
14 />
15 {suggestions.map(suggestion => (
16 <div key={suggestion.id}>{suggestion.text}</div>
17 ))}
18 </div>
19 );
20}Shared State (Lifted Up):
When multiple components need the same data, lift state to their common parent:
1function ShoppingApp() {
2 // Lifted state - multiple components need this data
3 const [cartItems, setCartItems] = useState([]);
4 const [user, setUser] = useState(null);
5
6 const addToCart = (product) => {
7 setCartItems(prev => [...prev, { ...product, id: Date.now() }]);
8 };
9
10 const removeFromCart = (itemId) => {
11 setCartItems(prev => prev.filter(item => item.id !== itemId));
12 };
13
14 return (
15 <div>
16 <Header user={user} cartCount={cartItems.length} />
17 <ProductList onAddToCart={addToCart} />
18 <ShoppingCart
19 items={cartItems}
20 onRemoveItem={removeFromCart}
21 user={user}
22 />
23 </div>
24 );
25}State Structure Patterns:
Normalized State:
For complex data structures, normalize your state:
1function BlogApp() {
2 // ❌ Nested structure is hard to update
3 const [posts, setPosts] = useState([
4 {
5 id: 1,
6 title: 'Post 1',
7 author: { id: 1, name: 'Alice' },
8 comments: [
9 { id: 1, text: 'Great post!', author: { id: 2, name: 'Bob' } }
10 ]
11 }
12 ]);
13
14 // ✅ Normalized structure is easier to manage
15 const [entities, setEntities] = useState({
16 posts: {
17 1: { id: 1, title: 'Post 1', authorId: 1, commentIds: [1] }
18 },
19 users: {
20 1: { id: 1, name: 'Alice' },
21 2: { id: 2, name: 'Bob' }
22 },
23 comments: {
24 1: { id: 1, text: 'Great post!', authorId: 2, postId: 1 }
25 }
26 });
27
28 // Helper selectors
29 const getPost = (postId) => entities.posts[postId];
30 const getUser = (userId) => entities.users[userId];
31 const getPostComments = (postId) =>
32 getPost(postId).commentIds.map(id => entities.comments[id]);
33
34 return (
35 <div>
36 {Object.values(entities.posts).map(post => (
37 <BlogPost
38 key={post.id}
39 post={post}
40 author={getUser(post.authorId)}
41 comments={getPostComments(post.id)}
42 />
43 ))}
44 </div>
45 );
46}State Update Patterns:
Immutable Updates:
Always create new objects/arrays when updating state:
1function TodoApp() {
2 const [todos, setTodos] = useState([]);
3
4 // ✅ Add item immutably
5 const addTodo = (text) => {
6 setTodos(prev => [
7 ...prev,
8 { id: Date.now(), text, completed: false }
9 ]);
10 };
11
12 // ✅ Update item immutably
13 const toggleTodo = (id) => {
14 setTodos(prev =>
15 prev.map(todo =>
16 todo.id === id
17 ? { ...todo, completed: !todo.completed }
18 : todo
19 )
20 );
21 };
22
23 // ✅ Remove item immutably
24 const removeTodo = (id) => {
25 setTodos(prev => prev.filter(todo => todo.id !== id));
26 };
27
28 // ✅ Update nested property immutably
29 const updateTodoText = (id, newText) => {
30 setTodos(prev =>
31 prev.map(todo =>
32 todo.id === id
33 ? { ...todo, text: newText }
34 : todo
35 )
36 );
37 };
38
39 return (
40 <TodoList
41 todos={todos}
42 onAdd={addTodo}
43 onToggle={toggleTodo}
44 onRemove={removeTodo}
45 onUpdateText={updateTodoText}
46 />
47 );
48}Reducer Pattern for Complex State:
For complex state logic, consider using useReducer:
1// State and actions
2const initialState = {
3 todos: [],
4 filter: 'all', // 'all', 'active', 'completed'
5 loading: false,
6 error: null
7};
8
9function todoReducer(state, action) {
10 switch (action.type) {
11 case 'ADD_TODO':
12 return {
13 ...state,
14 todos: [...state.todos, {
15 id: Date.now(),
16 text: action.payload,
17 completed: false
18 }]
19 };
20
21 case 'TOGGLE_TODO':
22 return {
23 ...state,
24 todos: state.todos.map(todo =>
25 todo.id === action.payload
26 ? { ...todo, completed: !todo.completed }
27 : todo
28 )
29 };
30
31 case 'REMOVE_TODO':
32 return {
33 ...state,
34 todos: state.todos.filter(todo => todo.id !== action.payload)
35 };
36
37 case 'SET_FILTER':
38 return {
39 ...state,
40 filter: action.payload
41 };
42
43 case 'SET_LOADING':
44 return {
45 ...state,
46 loading: action.payload
47 };
48
49 case 'SET_ERROR':
50 return {
51 ...state,
52 error: action.payload,
53 loading: false
54 };
55
56 default:
57 return state;
58 }
59}
60
61function TodoApp() {
62 const [state, dispatch] = useReducer(todoReducer, initialState);
63 const { todos, filter, loading, error } = state;
64
65 // Action creators
66 const addTodo = (text) => {
67 dispatch({ type: 'ADD_TODO', payload: text });
68 };
69
70 const toggleTodo = (id) => {
71 dispatch({ type: 'TOGGLE_TODO', payload: id });
72 };
73
74 const removeTodo = (id) => {
75 dispatch({ type: 'REMOVE_TODO', payload: id });
76 };
77
78 const setFilter = (newFilter) => {
79 dispatch({ type: 'SET_FILTER', payload: newFilter });
80 };
81
82 // Derived state
83 const filteredTodos = todos.filter(todo => {
84 switch (filter) {
85 case 'active': return !todo.completed;
86 case 'completed': return todo.completed;
87 default: return true;
88 }
89 });
90
91 return (
92 <div>
93 {error && <div className="error">{error}</div>}
94 <TodoInput onAdd={addTodo} disabled={loading} />
95 <TodoFilter currentFilter={filter} onFilterChange={setFilter} />
96 <TodoList
97 todos={filteredTodos}
98 onToggle={toggleTodo}
99 onRemove={removeTodo}
100 loading={loading}
101 />
102 </div>
103 );
104}Data Flow Patterns:
Container/Presenter Pattern:
Separate data management from presentation:
1// Container component (handles data and logic)
2function UserListContainer() {
3 const [users, setUsers] = useState([]);
4 const [loading, setLoading] = useState(true);
5 const [searchTerm, setSearchTerm] = useState('');
6
7 useEffect(() => {
8 fetchUsers()
9 .then(setUsers)
10 .finally(() => setLoading(false));
11 }, []);
12
13 const filteredUsers = users.filter(user =>
14 user.name.toLowerCase().includes(searchTerm.toLowerCase())
15 );
16
17 const handleSearch = (term) => {
18 setSearchTerm(term);
19 };
20
21 const handleUserUpdate = (updatedUser) => {
22 setUsers(prev =>
23 prev.map(user =>
24 user.id === updatedUser.id ? updatedUser : user
25 )
26 );
27 };
28
29 return (
30 <UserListPresenter
31 users={filteredUsers}
32 loading={loading}
33 searchTerm={searchTerm}
34 onSearch={handleSearch}
35 onUserUpdate={handleUserUpdate}
36 />
37 );
38}
39
40// Presenter component (handles display only)
41function UserListPresenter({
42 users,
43 loading,
44 searchTerm,
45 onSearch,
46 onUserUpdate
47}) {
48 if (loading) {
49 return <div className="loading">Loading users...</div>;
50 }
51
52 return (
53 <div className="user-list">
54 <SearchInput
55 value={searchTerm}
56 onChange={onSearch}
57 placeholder="Search users..."
58 />
59
60 <div className="user-grid">
61 {users.map(user => (
62 <UserCard
63 key={user.id}
64 user={user}
65 onUpdate={onUserUpdate}
66 />
67 ))}
68 </div>
69
70 {users.length === 0 && (
71 <div className="empty-state">
72 No users found
73 </div>
74 )}
75 </div>
76 );
77}Custom Hooks for State Logic:
Extract reusable state logic into custom hooks:
1// Custom hook for form handling
2function useForm(initialValues, validationRules = {}) {
3 const [values, setValues] = useState(initialValues);
4 const [errors, setErrors] = useState({});
5 const [touched, setTouched] = useState({});
6
7 const setValue = (name, value) => {
8 setValues(prev => ({ ...prev, [name]: value }));
9
10 // Clear error when user starts typing
11 if (errors[name]) {
12 setErrors(prev => ({ ...prev, [name]: '' }));
13 }
14 };
15
16 const setFieldTouched = (name) => {
17 setTouched(prev => ({ ...prev, [name]: true }));
18 };
19
20 const validateField = (name, value) => {
21 const rule = validationRules[name];
22 return rule ? rule(value) : '';
23 };
24
25 const validateForm = () => {
26 const newErrors = {};
27 Object.keys(validationRules).forEach(field => {
28 const error = validateField(field, values[field]);
29 if (error) newErrors[field] = error;
30 });
31 setErrors(newErrors);
32 return Object.keys(newErrors).length === 0;
33 };
34
35 const reset = () => {
36 setValues(initialValues);
37 setErrors({});
38 setTouched({});
39 };
40
41 return {
42 values,
43 errors,
44 touched,
45 setValue,
46 setFieldTouched,
47 validateForm,
48 reset
49 };
50}
51
52// Usage of custom hook
53function ContactForm() {
54 const { values, errors, touched, setValue, setFieldTouched, validateForm, reset } = useForm(
55 { name: '', email: '', message: '' },
56 {
57 name: (value) => !value ? 'Name is required' : '',
58 email: (value) => {
59 if (!value) return 'Email is required';
60 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
61 return 'Invalid email format';
62 }
63 return '';
64 },
65 message: (value) => !value ? 'Message is required' : ''
66 }
67 );
68
69 const handleSubmit = (e) => {
70 e.preventDefault();
71 if (validateForm()) {
72 console.log('Form submitted:', values);
73 reset();
74 }
75 };
76
77 return (
78 <form onSubmit={handleSubmit}>
79 <FormField
80 label="Name"
81 name="name"
82 value={values.name}
83 error={touched.name ? errors.name : ''}
84 onChange={(name, value) => setValue(name, value)}
85 onBlur={(name) => setFieldTouched(name)}
86 />
87 {/* Other fields... */}
88 </form>
89 );
90}State Management Best Practices:
Understanding these patterns helps you build React applications that scale well and remain maintainable as they grow in complexity.
Master React state with hands-on examples of useState hook, controlled components, state vs props, and state management patterns. Build dynamic applications with proper data flow.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>React State & Props Interactive Demo</title>
7 <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
8 <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
9 <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
10</head>
11<body>
12 <div id="root"></div>
13
14 <script type="text/babel">
15 const { useState, useEffect } = React;
16
17 // Counter Component - Basic useState example
18 function Counter({ initialCount = 0, step = 1, label = "Counter" }) {
19 const [count, setCount] = useState(initialCount);
20
21 const increment = () => setCount(prev => prev + step);
22 const decrement = () => setCount(prev => prev - step);
23 const reset = () => setCount(initialCount);
24
25 // Derived state - computed from existing state
26 const isEven = count % 2 === 0;
27 const isPositive = count > 0;
28
29 return (
30 <div className="counter-widget">
31 <h3>{label}</h3>
32 <div className="counter-display">
33 <span className="count-number">{count}</span>
34 <div className="count-info">
35 <span className={isEven ? 'badge-success' : 'badge-warning'}>
36 {isEven ? 'Even' : 'Odd'}
37 </span>
38 <span className={isPositive ? 'badge-primary' : 'badge-secondary'}>
39 {isPositive ? 'Positive' : 'Zero/Negative'}
40 </span>
41 </div>
42 </div>
43 <div className="counter-buttons">
44 <button className="btn btn-secondary" onClick={decrement}>
45 -{step}
46 </button>
47 <button className="btn btn-warning" onClick={reset}>
48 Reset
49 </button>
50 <button className="btn btn-primary" onClick={increment}>
51 +{step}
52 </button>
53 </div>
54 </div>
55 );
56 }
57
58 // Controlled Form Component
59 function ContactForm({ onSubmit }) {
60 const [formData, setFormData] = useState({
61 name: '',
62 email: '',
63 phone: '',
64 message: '',
65 priority: 'medium',
66 newsletter: false
67 });
68
69 const [errors, setErrors] = useState({});
70 const [isSubmitting, setIsSubmitting] = useState(false);
71
72 // Generic handler for all form fields
73 const handleChange = (e) => {
74 const { name, value, type, checked } = e.target;
75 const fieldValue = type === 'checkbox' ? checked : value;
76
77 setFormData(prev => ({
78 ...prev,
79 [name]: fieldValue
80 }));
81
82 // Clear error when user starts typing
83 if (errors[name]) {
84 setErrors(prev => ({
85 ...prev,
86 [name]: ''
87 }));
88 }
89 };
90
91 const validateForm = () => {
92 const newErrors = {};
93
94 if (!formData.name.trim()) {
95 newErrors.name = 'Name is required';
96 }
97
98 if (!formData.email.trim()) {
99 newErrors.email = 'Email is required';
100 } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
101 newErrors.email = 'Please enter a valid email';
102 }
103
104 if (!formData.message.trim()) {
105 newErrors.message = 'Message is required';
106 } else if (formData.message.trim().length < 10) {
107 newErrors.message = 'Message must be at least 10 characters';
108 }
109
110 return newErrors;
111 };
112
113 const handleSubmit = async (e) => {
114 e.preventDefault();
115
116 const formErrors = validateForm();
117
118 if (Object.keys(formErrors).length > 0) {
119 setErrors(formErrors);
120 return;
121 }
122
123 setIsSubmitting(true);
124
125 try {
126 // Simulate API call
127 await new Promise(resolve => setTimeout(resolve, 1500));
128
129 onSubmit(formData);
130
131 // Reset form
132 setFormData({
133 name: '',
134 email: '',
135 phone: '',
136 message: '',
137 priority: 'medium',
138 newsletter: false
139 });
140 setErrors({});
141 } catch (error) {
142 setErrors({ submit: 'Failed to send message. Please try again.' });
143 } finally {
144 setIsSubmitting(false);
145 }
146 };
147
148 return (
149 <form onSubmit={handleSubmit} className="contact-form">
150 <h3>Contact Us</h3>
151
152 <div className="form-row">
153 <div className="form-group">
154 <label htmlFor="name">Name *</label>
155 <input
156 type="text"
157 id="name"
158 name="name"
159 value={formData.name}
160 onChange={handleChange}
161 className={errors.name ? 'error' : ''}
162 disabled={isSubmitting}
163 />
164 {errors.name && <span className="error-text">{errors.name}</span>}
165 </div>
166
167 <div className="form-group">
168 <label htmlFor="email">Email *</label>
169 <input
170 type="email"
171 id="email"
172 name="email"
173 value={formData.email}
174 onChange={handleChange}
175 className={errors.email ? 'error' : ''}
176 disabled={isSubmitting}
177 />
178 {errors.email && <span className="error-text">{errors.email}</span>}
179 </div>
180 </div>
181
182 <div className="form-group">
183 <label htmlFor="phone">Phone</label>
184 <input
185 type="tel"
186 id="phone"
187 name="phone"
188 value={formData.phone}
189 onChange={handleChange}
190 disabled={isSubmitting}
191 placeholder="Optional"
192 />
193 </div>
194
195 <div className="form-group">
196 <label htmlFor="priority">Priority</label>
197 <select
198 id="priority"
199 name="priority"
200 value={formData.priority}
201 onChange={handleChange}
202 disabled={isSubmitting}
203 >
204 <option value="low">Low</option>
205 <option value="medium">Medium</option>
206 <option value="high">High</option>
207 <option value="urgent">Urgent</option>
208 </select>
209 </div>
210
211 <div className="form-group">
212 <label htmlFor="message">Message *</label>
213 <textarea
214 id="message"
215 name="message"
216 value={formData.message}
217 onChange={handleChange}
218 className={errors.message ? 'error' : ''}
219 disabled={isSubmitting}
220 rows={4}
221 placeholder="Tell us how we can help..."
222 />
223 <div className="char-count">
224 {formData.message.length} characters
225 </div>
226 {errors.message && <span className="error-text">{errors.message}</span>}
227 </div>
228
229 <div className="form-group checkbox-group">
230 <label className="checkbox-label">
231 <input
232 type="checkbox"
233 name="newsletter"
234 checked={formData.newsletter}
235 onChange={handleChange}
236 disabled={isSubmitting}
237 />
238 Subscribe to our newsletter
239 </label>
240 </div>
241
242 {errors.submit && <div className="error-text">{errors.submit}</div>}
243
244 <button
245 type="submit"
246 disabled={isSubmitting}
247 className={`btn btn-primary submit-btn ${isSubmitting ? 'loading' : ''}`}
248 >
249 {isSubmitting ? 'Sending...' : 'Send Message'}
250 </button>
251 </form>
252 );
253 }
254
255 // Todo List Component - Complex state management
256 function TodoApp() {
257 const [todos, setTodos] = useState([
258 { id: 1, text: 'Learn React State', completed: false },
259 { id: 2, text: 'Build a Todo App', completed: false },
260 { id: 3, text: 'Master Props vs State', completed: true }
261 ]);
262 const [inputText, setInputText] = useState('');
263 const [filter, setFilter] = useState('all'); // 'all', 'active', 'completed'
264
265 const addTodo = () => {
266 if (inputText.trim()) {
267 setTodos(prev => [
268 ...prev,
269 {
270 id: Date.now(),
271 text: inputText.trim(),
272 completed: false
273 }
274 ]);
275 setInputText('');
276 }
277 };
278
279 const toggleTodo = (id) => {
280 setTodos(prev =>
281 prev.map(todo =>
282 todo.id === id
283 ? { ...todo, completed: !todo.completed }
284 : todo
285 )
286 );
287 };
288
289 const removeTodo = (id) => {
290 setTodos(prev => prev.filter(todo => todo.id !== id));
291 };
292
293 const editTodo = (id, newText) => {
294 setTodos(prev =>
295 prev.map(todo =>
296 todo.id === id
297 ? { ...todo, text: newText }
298 : todo
299 )
300 );
301 };
302
303 const clearCompleted = () => {
304 setTodos(prev => prev.filter(todo => !todo.completed));
305 };
306
307 // Derived state - computed from todos and filter
308 const filteredTodos = todos.filter(todo => {
309 switch (filter) {
310 case 'active': return !todo.completed;
311 case 'completed': return todo.completed;
312 default: return true;
313 }
314 });
315
316 const stats = {
317 total: todos.length,
318 completed: todos.filter(t => t.completed).length,
319 active: todos.filter(t => !t.completed).length
320 };
321
322 return (
323 <div className="todo-app">
324 <h3>Todo List</h3>
325
326 <div className="todo-input-section">
327 <div className="input-group">
328 <input
329 type="text"
330 value={inputText}
331 onChange={(e) => setInputText(e.target.value)}
332 onKeyPress={(e) => e.key === 'Enter' && addTodo()}
333 placeholder="Add a new todo..."
334 className="todo-input"
335 />
336 <button onClick={addTodo} className="btn btn-primary">
337 Add
338 </button>
339 </div>
340 </div>
341
342 <div className="todo-filters">
343 <button
344 className={filter === 'all' ? 'btn btn-primary' : 'btn btn-secondary'}
345 onClick={() => setFilter('all')}
346 >
347 All ({stats.total})
348 </button>
349 <button
350 className={filter === 'active' ? 'btn btn-primary' : 'btn btn-secondary'}
351 onClick={() => setFilter('active')}
352 >
353 Active ({stats.active})
354 </button>
355 <button
356 className={filter === 'completed' ? 'btn btn-primary' : 'btn btn-secondary'}
357 onClick={() => setFilter('completed')}
358 >
359 Completed ({stats.completed})
360 </button>
361 </div>
362
363 <div className="todo-list">
364 {filteredTodos.length === 0 ? (
365 <div className="empty-state">
366 {filter === 'all' ? 'No todos yet' :
367 filter === 'active' ? 'No active todos' :
368 'No completed todos'}
369 </div>
370 ) : (
371 filteredTodos.map(todo => (
372 <TodoItem
373 key={todo.id}
374 todo={todo}
375 onToggle={toggleTodo}
376 onRemove={removeTodo}
377 onEdit={editTodo}
378 />
379 ))
380 )}
381 </div>
382
383 {stats.completed > 0 && (
384 <div className="todo-actions">
385 <button
386 onClick={clearCompleted}
387 className="btn btn-warning"
388 >
389 Clear Completed ({stats.completed})
390 </button>
391 </div>
392 )}
393 </div>
394 );
395 }
396
397 // Todo Item Component - Props example
398 function TodoItem({ todo, onToggle, onRemove, onEdit }) {
399 const [isEditing, setIsEditing] = useState(false);
400 const [editText, setEditText] = useState(todo.text);
401
402 const handleSave = () => {
403 if (editText.trim() && editText !== todo.text) {
404 onEdit(todo.id, editText.trim());
405 }
406 setIsEditing(false);
407 };
408
409 const handleCancel = () => {
410 setEditText(todo.text);
411 setIsEditing(false);
412 };
413
414 const handleKeyPress = (e) => {
415 if (e.key === 'Enter') {
416 handleSave();
417 } else if (e.key === 'Escape') {
418 handleCancel();
419 }
420 };
421
422 return (
423 <div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
424 <input
425 type="checkbox"
426 checked={todo.completed}
427 onChange={() => onToggle(todo.id)}
428 className="todo-checkbox"
429 />
430
431 {isEditing ? (
432 <div className="todo-edit-mode">
433 <input
434 type="text"
435 value={editText}
436 onChange={(e) => setEditText(e.target.value)}
437 onKeyPress={handleKeyPress}
438 onBlur={handleSave}
439 className="todo-edit-input"
440 autoFocus
441 />
442 </div>
443 ) : (
444 <div
445 className="todo-text"
446 onDoubleClick={() => setIsEditing(true)}
447 >
448 {todo.text}
449 </div>
450 )}
451
452 <div className="todo-actions">
453 {!isEditing && (
454 <button
455 onClick={() => setIsEditing(true)}
456 className="btn btn-sm btn-secondary"
457 >
458 Edit
459 </button>
460 )}
461 <button
462 onClick={() => onRemove(todo.id)}
463 className="btn btn-sm btn-danger"
464 >
465 Delete
466 </button>
467 </div>
468 </div>
469 );
470 }
471
472 // Main App Component
473 function App() {
474 const [messages, setMessages] = useState([]);
475
476 const handleFormSubmit = (formData) => {
477 const message = {
478 id: Date.now(),
479 ...formData,
480 timestamp: new Date().toLocaleString()
481 };
482 setMessages(prev => [message, ...prev]);
483 };
484
485 return (
486 <div className="app">
487 <div className="header">
488 <h1>⚛️ React State & Props Demo</h1>
489 <p>Interactive examples of state management and data flow</p>
490 </div>
491
492 <div className="demo-grid">
493 {/* State Examples */}
494 <div className="demo-section">
495 <h2>useState Hook Examples</h2>
496 <div className="counters-grid">
497 <Counter initialCount={0} step={1} label="Basic Counter" />
498 <Counter initialCount={10} step={5} label="Step by 5" />
499 <Counter initialCount={-5} step={2} label="Negative Start" />
500 </div>
501 </div>
502
503 {/* Controlled Components */}
504 <div className="demo-section full-width">
505 <h2>Controlled Components</h2>
506 <ContactForm onSubmit={handleFormSubmit} />
507 </div>
508
509 {/* Complex State Management */}
510 <div className="demo-section">
511 <h2>Complex State Management</h2>
512 <TodoApp />
513 </div>
514
515 {/* Messages Display */}
516 {messages.length > 0 && (
517 <div className="demo-section">
518 <h2>Form Submissions</h2>
519 <div className="messages-list">
520 {messages.map(message => (
521 <div key={message.id} className="message-card">
522 <div className="message-header">
523 <strong>{message.name}</strong>
524 <span className="timestamp">{message.timestamp}</span>
525 </div>
526 <div className="message-body">
527 <p><strong>Email:</strong> {message.email}</p>
528 {message.phone && <p><strong>Phone:</strong> {message.phone}</p>}
529 <p><strong>Priority:</strong>
530 <span className={`priority-${message.priority}`}>
531 {message.priority.toUpperCase()}
532 </span>
533 </p>
534 <p><strong>Message:</strong> {message.message}</p>
535 {message.newsletter && (
536 <p className="newsletter">✅ Subscribed to newsletter</p>
537 )}
538 </div>
539 </div>
540 ))}
541 </div>
542 </div>
543 )}
544 </div>
545
546 <div className="explanation">
547 <h2>🎯 Key Concepts Demonstrated</h2>
548 <div className="concepts-grid">
549 <div className="concept-card">
550 <h3>useState Hook</h3>
551 <p>Adding state to functional components with useState. See how state updates trigger re-renders.</p>
552 </div>
553 <div className="concept-card">
554 <h3>Controlled Components</h3>
555 <p>Form inputs controlled by React state. Single source of truth for form data.</p>
556 </div>
557 <div className="concept-card">
558 <h3>Props vs State</h3>
559 <p>Props flow down from parent to child. State belongs to the component and can change.</p>
560 </div>
561 <div className="concept-card">
562 <h3>State Updates</h3>
563 <p>Always use immutable updates. Use functional updates when new state depends on previous state.</p>
564 </div>
565 <div className="concept-card">
566 <h3>Derived State</h3>
567 <p>Compute values from existing state instead of storing them separately.</p>
568 </div>
569 <div className="concept-card">
570 <h3>Data Flow</h3>
571 <p>Props down, events up. State flows down the component tree via props.</p>
572 </div>
573 </div>
574 </div>
575 </div>
576 );
577 }
578
579 // Render the App
580 ReactDOM.render(<App />, document.getElementById('root'));
581 </script>
582</body>
583</html>This comprehensive demonstration showcases advanced React state management concepts:
🎯 Complete State & Props Coverage:
1. useState Hook Mastery:
2. Controlled Components:
3. Props vs State Understanding:
4. Complex State Patterns:
5. State Management Best Practices:
6. Advanced Form Handling:
7. Interactive State Examples:
8. Component Communication:
9. Real-world Patterns:
10. Performance Considerations:
This demonstration provides hands-on experience with all aspects of React state management, showing how state and props work together to create dynamic, interactive applications. Students can see immediate results and understand the relationship between state changes and UI updates.
Outstanding work mastering React state and props! You now have a solid understanding of state management, controlled components, and data flow patterns. You're ready to build complex interactive applications with React. Next, you could explore advanced topics like useEffect for side effects, custom hooks for reusable logic, or state management libraries like Redux for larger applications. Keep practicing these patterns as they're fundamental to React development!
Your progress is saved in this browser, on this device only.