⚡ State and Events

Making Your Components Interactive

What is State?

State is data that changes over time. When state changes, React automatically re-renders your component. Think of it like a component's memory.

Props vs State:

  • Props: Passed from parent, read-only, like function arguments
  • State: Internal to component, mutable, like local variables

🎯 useState Hook

The most important React hook for managing state:

import { useState } from 'react';

function Counter() {
  // Declare state variable
  const [count, setCount] = useState(0);
  //     ↑      ↑           ↑
  //   value  updater   initial value
  
  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

How useState Works:

📊 Different Types of State

1. Number State:

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

2. String State:

function NameInput() {
  const [name, setName] = useState('');
  
  return (
    <div>
      <input 
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <p>Hello, {name || 'Stranger'}!</p>
    </div>
  );
}

3. Boolean State:

function Toggle() {
  const [isOn, setIsOn] = useState(false);
  
  return (
    <div>
      <p>The light is {isOn ? 'ON' : 'OFF'}</p>
      <button onClick={() => setIsOn(!isOn)}>
        Toggle
      </button>
    </div>
  );
}

4. Array State:

function TodoList() {
  const [todos, setTodos] = useState(['Learn React', 'Build App']);
  const [input, setInput] = useState('');
  
  const addTodo = () => {
    if (input.trim()) {
      setTodos([...todos, input]);  // Spread operator to add
      setInput('');
    }
  };
  
  const removeTodo = (index) => {
    setTodos(todos.filter((_, i) => i !== index));
  };
  
  return (
    <div>
      <input 
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="New todo"
      />
      <button onClick={addTodo}>Add</button>
      
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => removeTodo(index)}>×</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

5. Object State:

function UserForm() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });
  
  const handleChange = (field, value) => {
    setUser({
      ...user,        // Spread existing properties
      [field]: value  // Update specific field
    });
  };
  
  return (
    <div>
      <input 
        value={user.name}
        onChange={(e) => handleChange('name', e.target.value)}
        placeholder="Name"
      />
      <input 
        value={user.email}
        onChange={(e) => handleChange('email', e.target.value)}
        placeholder="Email"
      />
      <input 
        type="number"
        value={user.age}
        onChange={(e) => handleChange('age', e.target.value)}
        placeholder="Age"
      />
      
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <p>Age: {user.age}</p>
    </div>
  );
}

🖱️ Event Handling

Common Events:

function EventExamples() {
  const [message, setMessage] = useState('');
  
  // Click event
  const handleClick = () => {
    setMessage('Button clicked!');
  };
  
  // Change event (input)
  const handleChange = (e) => {
    setMessage(e.target.value);
  };
  
  // Submit event (form)
  const handleSubmit = (e) => {
    e.preventDefault();  // Prevent page reload
    alert('Form submitted!');
  };
  
  // Mouse events
  const handleMouseEnter = () => {
    setMessage('Mouse entered!');
  };
  
  return (
    <div>
      {/* Click */}
      <button onClick={handleClick}>Click Me</button>
      
      {/* Change */}
      <input onChange={handleChange} placeholder="Type here" />
      
      {/* Submit */}
      <form onSubmit={handleSubmit}>
        <button type="submit">Submit</button>
      </form>
      
      {/* Mouse events */}
      <div 
        onMouseEnter={handleMouseEnter}
        onMouseLeave={() => setMessage('Mouse left!')}
      >
        Hover over me
      </div>
      
      <p>{message}</p>
    </div>
  );
}

Event Object Properties:

function EventInfo() {
  const handleEvent = (e) => {
    console.log('Event type:', e.type);
    console.log('Target element:', e.target);
    console.log('Target value:', e.target.value);
    console.log('Key pressed:', e.key);  // For keyboard events
  };
  
  return (
    <input 
      onChange={handleEvent}
      onKeyDown={handleEvent}
    />
  );
}

📝 Forms in React

Controlled Components:

React controls the form input values (recommended approach):

function LoginForm() {
  const [formData, setFormData] = useState({
    username: '',
    password: ''
  });
  
  const [submitted, setSubmitted] = useState(false);
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({
      ...formData,
      [name]: value
    });
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Login attempt:', formData);
    setSubmitted(true);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Username:</label>
        <input 
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
          required
        />
      </div>
      
      <div>
        <label>Password:</label>
        <input 
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
          required
        />
      </div>
      
      <button type="submit">>Login</button>
      
      {submitted && (
        <p>Welcome, {formData.username}!</p>
      )}
    </form>
  );
}

Different Input Types:

function FormInputs() {
  const [data, setData] = useState({
    text: '',
    email: '',
    number: 0,
    checkbox: false,
    radio: '',
    select: ''
  });
  
  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setData({
      ...data,
      [name]: type === 'checkbox' ? checked : value
    });
  };
  
  return (
    <form>
      {/* Text input */}
      <input 
        type="text"
        name="text"
        value={data.text}
        onChange={handleChange}
      />
      
      {/* Email input */}
      <input 
        type="email"
        name="email"
        value={data.email}
        onChange={handleChange}
      />
      
      {/* Number input */}
      <input 
        type="number"
        name="number"
        value={data.number}
        onChange={handleChange}
      />
      
      {/* Checkbox */}
      <label>
        <input 
          type="checkbox"
          name="checkbox"
          checked={data.checkbox}
          onChange={handleChange}
        />
        Accept terms
      </label>
      
      {/* Radio buttons */}
      <label>
        <input 
          type="radio"
          name="radio"
          value="option1"
          checked={data.radio === 'option1'}
          onChange={handleChange}
        />
        Option 1
      </label>
      
      {/* Select dropdown */}
      <select name="select" value={data.select} onChange={handleChange}>
        <option value="">Choose...</option>
        <option value="a">Option A</option>
        <option value="b">Option B</option>
      </select>
    </form>
  );
}

✅ Form Validation

function SignupForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    confirmPassword: ''
  });
  
  const [errors, setErrors] = useState({});
  
  const validate = () => {
    const newErrors = {};
    
    // Email validation
    if (!formData.email) {
      newErrors.email = 'Email is required';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email is invalid';
    }
    
    // Password validation
    if (!formData.password) {
      newErrors.password = 'Password is required';
    } else if (formData.password.length < 6) {
      newErrors.password = 'Password must be at least 6 characters';
    }
    
    // Confirm password
    if (formData.password !== formData.confirmPassword) {
      newErrors.confirmPassword = 'Passwords do not match';
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
    // Clear error when user types
    if (errors[name]) {
      setErrors({ ...errors, [name]: '' });
    }
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      alert('Form is valid!');
      console.log('Submitting:', formData);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input 
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          placeholder="Email"
        />
        {errors.email && <p className="error">{errors.email}</p>}
      </div>
      
      <div>
        <input 
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
          placeholder="Password"
        />
        {errors.password && <p className="error">{errors.password}</p>}
      </div>
      
      <div>
        <input 
          type="password"
          name="confirmPassword"
          value={formData.confirmPassword}
          onChange={handleChange}
          placeholder="Confirm Password"
        />
        {errors.confirmPassword && <p className="error">{errors.confirmPassword}</p>}
      </div>
      
      <button type="submit">Sign Up</button>
    </form>
  );
}

🎯 Complete Example: Shopping Cart

function ShoppingCart() {
  const [items, setItems] = useState([
    { id: 1, name: 'Laptop', price: 999, quantity: 1 },
    { id: 2, name: 'Mouse', price: 29, quantity: 2 }
  ]);
  
  // Increase quantity
  const increaseQuantity = (id) => {
    setItems(items.map(item => 
      item.id === id 
        ? { ...item, quantity: item.quantity + 1 }
        : item
    ));
  };
  
  // Decrease quantity
  const decreaseQuantity = (id) => {
    setItems(items.map(item => 
      item.id === id && item.quantity > 1
        ? { ...item, quantity: item.quantity - 1 }
        : item
    ));
  };
  
  // Remove item
  const removeItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };
  
  // Calculate total
  const total = items.reduce((sum, item) => 
    sum + (item.price * item.quantity), 0
  );
  
  return (
    <div className="cart">
      <h2>Shopping Cart</h2>
      
      {items.length === 0 ? (
        <p>Your cart is empty</p>
      ) : (
        <>
          {items.map(item => (
            <div key={item.id} className="cart-item">
              <h3>{item.name}</h3>
              <p>${item.price}</p>
              
              <div className="quantity-controls">
                <button onClick={() => decreaseQuantity(item.id)}>-</button>
                <span>{item.quantity}</span>
                <button onClick={() => increaseQuantity(item.id)}>+</button>
              </div>
              
              <p>Subtotal: ${item.price * item.quantity}</p>
              
              <button onClick={() => removeItem(item.id)}>Remove</button>
            </div>
          ))}
          
          <div className="cart-total">
            <h3>Total: ${total.toFixed(2)}</h3>
            <button onClick={() => alert('Checkout!')}>
              Checkout
            </button>
          </div>
        </>
      )}
    </div>
  );
}

⚠️ Common Mistakes

1. Modifying State Directly

// ❌ Wrong - mutates state
const [count, setCount] = useState(0);
count = count + 1;  // Don't do this!

// ✅ Correct - use setter
setCount(count + 1);

2. Not Using Previous State

// ❌ Might miss updates
setCount(count + 1);

// ✅ Better - uses previous state
setCount(prevCount => prevCount + 1);

3. Forgetting e.preventDefault()

// ❌ Page will reload
const handleSubmit = (e) => {
  console.log('Submitted');
};

// ✅ Prevents default behavior
const handleSubmit = (e) => {
  e.preventDefault();
  console.log('Submitted');
};

🎯 Key Takeaways