React Intermediate

Event Handling and Forms in React - Building Interactive UIs

CodingerWeb
CodingerWeb
26 views 65 min read

Event Handling in React

Event handling is crucial for creating interactive React applications. React uses SyntheticEvents, which are wrappers around native DOM events that provide consistent behavior across different browsers.

Basic Event Handling

React events are named using camelCase and pass a SyntheticEvent object:

function EventExamples() {
  const handleClick = (event) => {
    console.log("Button clicked!", event);
    console.log("Event type:", event.type);
    console.log("Target element:", event.target);
  };
  
  const handleMouseOver = (event) => {
    event.target.style.backgroundColor = "lightblue";
  };
  
  const handleMouseOut = (event) => {
    event.target.style.backgroundColor = "";
  };
  
  const handleKeyPress = (event) => {
    if (event.key === "Enter") {
      console.log("Enter key pressed!");
    }
  };
  
  return (
    <div>
      <button onClick={handleClick}>
        Click Me
      </button>
      
      <div 
        onMouseOver={handleMouseOver}
        onMouseOut={handleMouseOut}
        style={{ padding: "20px", border: "1px solid #ccc", margin: "10px" }}
      >
        Hover over me!
      </div>
      
      <input 
        type="text"
        onKeyPress={handleKeyPress}
        placeholder="Press Enter"
      />
    </div>
  );
}

Event Object and preventDefault

The event object provides useful methods and properties:

function LinkExample() {
  const handleLinkClick = (event) => {
    event.preventDefault(); // Prevent default link behavior
    console.log("Link clicked, but navigation prevented");
    
    // Custom logic here
    alert("Custom link handler executed!");
  };
  
  const handleFormSubmit = (event) => {
    event.preventDefault(); // Prevent form submission
    console.log("Form submission prevented");
    
    // Handle form data manually
    const formData = new FormData(event.target);
    console.log("Form data:", Object.fromEntries(formData));
  };
  
  return (
    <div>
      <a href="https://example.com" onClick={handleLinkClick}>
        Click this link (won't navigate)
      </a>
      
      <form onSubmit={handleFormSubmit}>
        <input name="username" placeholder="Username" required />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

Passing Parameters to Event Handlers

There are several ways to pass additional parameters:

function ParameterExamples() {
  const [selectedItem, setSelectedItem] = useState(null);
  
  const items = [
    { id: 1, name: "Apple", category: "Fruit" },
    { id: 2, name: "Carrot", category: "Vegetable" },
    { id: 3, name: "Banana", category: "Fruit" }
  ];
  
  // Method 1: Arrow function in JSX
  const handleItemClick1 = (item) => {
    setSelectedItem(item);
    console.log("Selected:", item.name);
  };
  
  // Method 2: Bind method
  const handleItemClick2 = (item, event) => {
    setSelectedItem(item);
    console.log("Selected:", item.name, "Event:", event);
  };
  
  // Method 3: Data attributes
  const handleItemClick3 = (event) => {
    const itemId = parseInt(event.target.dataset.itemId);
    const item = items.find(i => i.id === itemId);
    setSelectedItem(item);
  };
  
  return (
    <div>
      <h3>Method 1: Arrow Function</h3>
      {items.map(item => (
        <button 
          key={item.id}
          onClick={() => handleItemClick1(item)}
        >
          {item.name}
        </button>
      ))}
      
      <h3>Method 2: Bind</h3>
      {items.map(item => (
        <button 
          key={item.id}
          onClick={handleItemClick2.bind(null, item)}
        >
          {item.name}
        </button>
      ))}
      
      <h3>Method 3: Data Attributes</h3>
      {items.map(item => (
        <button 
          key={item.id}
          data-item-id={item.id}
          onClick={handleItemClick3}
        >
          {item.name}
        </button>
      ))}
      
      {selectedItem && (
        <div>
          <h4>Selected: {selectedItem.name}</h4>
          <p>Category: {selectedItem.category}</p>
        </div>
      )}
    </div>
  );
}

Form Handling in React

React provides two approaches for handling forms: controlled and uncontrolled components.

Controlled Components

Form elements whose values are controlled by React state:

function ControlledForm() {
  const [formData, setFormData] = useState({
    firstName: "",
    lastName: "",
    email: "",
    age: "",
    gender: "",
    country: "",
    interests: [],
    newsletter: false,
    comments: ""
  });
  
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  const handleInputChange = (event) => {
    const { name, value, type, checked } = event.target;
    
    setFormData(prevData => ({
      ...prevData,
      [name]: type === "checkbox" ? checked : value
    }));
    
    // Clear error when user starts typing
    if (errors[name]) {
      setErrors(prevErrors => ({
        ...prevErrors,
        [name]: ""
      }));
    }
  };
  
  const handleInterestChange = (event) => {
    const { value, checked } = event.target;
    
    setFormData(prevData => ({
      ...prevData,
      interests: checked
        ? [...prevData.interests, value]
        : prevData.interests.filter(interest => interest !== value)
    }));
  };
  
  const validateForm = () => {
    const newErrors = {};
    
    if (!formData.firstName.trim()) {
      newErrors.firstName = "First name is required";
    }
    
    if (!formData.lastName.trim()) {
      newErrors.lastName = "Last name is required";
    }
    
    if (!formData.email.trim()) {
      newErrors.email = "Email is required";
    } else if (!/S+@S+.S+/.test(formData.email)) {
      newErrors.email = "Email is invalid";
    }
    
    if (!formData.age) {
      newErrors.age = "Age is required";
    } else if (formData.age < 1 || formData.age > 120) {
      newErrors.age = "Age must be between 1 and 120";
    }
    
    if (!formData.gender) {
      newErrors.gender = "Please select a gender";
    }
    
    if (!formData.country) {
      newErrors.country = "Please select a country";
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const handleSubmit = async (event) => {
    event.preventDefault();
    
    if (!validateForm()) {
      return;
    }
    
    setIsSubmitting(true);
    
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 2000));
      
      console.log("Form submitted:", formData);
      alert("Form submitted successfully!");
      
      // Reset form
      setFormData({
        firstName: "",
        lastName: "",
        email: "",
        age: "",
        gender: "",
        country: "",
        interests: [],
        newsletter: false,
        comments: ""
      });
    } catch (error) {
      console.error("Submission error:", error);
      alert("Submission failed. Please try again.");
    } finally {
      setIsSubmitting(false);
    }
  };
  
  return (
    <form onSubmit={handleSubmit} className="controlled-form">
      <h2>User Registration Form</h2>
      
      <div className="form-row">
        <div className="form-group">
          <label htmlFor="firstName">First Name:</label>
          <input
            type="text"
            id="firstName"
            name="firstName"
            value={formData.firstName}
            onChange={handleInputChange}
            className={errors.firstName ? "error" : ""}
          />
          {errors.firstName && <span className="error-message">{errors.firstName}</span>}
        </div>
        
        <div className="form-group">
          <label htmlFor="lastName">Last Name:</label>
          <input
            type="text"
            id="lastName"
            name="lastName"
            value={formData.lastName}
            onChange={handleInputChange}
            className={errors.lastName ? "error" : ""}
          />
          {errors.lastName && <span className="error-message">{errors.lastName}</span>}
        </div>
      </div>
      
      <div className="form-group">
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleInputChange}
          className={errors.email ? "error" : ""}
        />
        {errors.email && <span className="error-message">{errors.email}</span>}
      </div>
      
      <div className="form-group">
        <label htmlFor="age">Age:</label>
        <input
          type="number"
          id="age"
          name="age"
          value={formData.age}
          onChange={handleInputChange}
          className={errors.age ? "error" : ""}
        />
        {errors.age && <span className="error-message">{errors.age}</span>}
      </div>
      
      <div className="form-group">
        <label>Gender:</label>
        <div className="radio-group">
          <label>
            <input
              type="radio"
              name="gender"
              value="male"
              checked={formData.gender === "male"}
              onChange={handleInputChange}
            />
            Male
          </label>
          <label>
            <input
              type="radio"
              name="gender"
              value="female"
              checked={formData.gender === "female"}
              onChange={handleInputChange}
            />
            Female
          </label>
          <label>
            <input
              type="radio"
              name="gender"
              value="other"
              checked={formData.gender === "other"}
              onChange={handleInputChange}
            />
            Other
          </label>
        </div>
        {errors.gender && <span className="error-message">{errors.gender}</span>}
      </div>
      
      <div className="form-group">
        <label htmlFor="country">Country:</label>
        <select
          id="country"
          name="country"
          value={formData.country}
          onChange={handleInputChange}
          className={errors.country ? "error" : ""}
        >
          <option value="">Select a country</option>
          <option value="us">United States</option>
          <option value="ca">Canada</option>
          <option value="uk">United Kingdom</option>
          <option value="au">Australia</option>
          <option value="de">Germany</option>
        </select>
        {errors.country && <span className="error-message">{errors.country}</span>}
      </div>
      
      <div className="form-group">
        <label>Interests:</label>
        <div className="checkbox-group">
          {["Programming", "Design", "Music", "Sports", "Reading"].map(interest => (
            <label key={interest}>
              <input
                type="checkbox"
                value={interest}
                checked={formData.interests.includes(interest)}
                onChange={handleInterestChange}
              />
              {interest}
            </label>
          ))}
        </div>
      </div>
      
      <div className="form-group">
        <label>
          <input
            type="checkbox"
            name="newsletter"
            checked={formData.newsletter}
            onChange={handleInputChange}
          />
          Subscribe to newsletter
        </label>
      </div>
      
      <div className="form-group">
        <label htmlFor="comments">Comments:</label>
        <textarea
          id="comments"
          name="comments"
          value={formData.comments}
          onChange={handleInputChange}
          rows="4"
          placeholder="Any additional comments..."
        ></textarea>
      </div>
      
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Submitting..." : "Submit"}
      </button>
      
      <div className="form-data-preview">
        <h3>Form Data Preview:</h3>
        <pre>{JSON.stringify(formData, null, 2)}</pre>
      </div>
    </form>
  );
}

Uncontrolled Components

Form elements that maintain their own state using refs:

import React, { useRef } from 'react';

function UncontrolledForm() {
  const formRef = useRef();
  const nameRef = useRef();
  const emailRef = useRef();
  const messageRef = useRef();
  
  const handleSubmit = (event) => {
    event.preventDefault();
    
    const formData = {
      name: nameRef.current.value,
      email: emailRef.current.value,
      message: messageRef.current.value
    };
    
    console.log("Uncontrolled form data:", formData);
    
    // Reset form
    formRef.current.reset();
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <h2>Contact Form (Uncontrolled)</h2>
      
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          ref={nameRef}
          defaultValue="John Doe"
          required
        />
      </div>
      
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          ref={emailRef}
          required
        />
      </div>
      
      <div>
        <label htmlFor="message">Message:</label>
        <textarea
          id="message"
          ref={messageRef}
          rows="4"
          required
        ></textarea>
      </div>
      
      <button type="submit">Send Message</button>
    </form>
  );
}

Advanced Event Handling Patterns

Some useful patterns for complex event handling:

function AdvancedEventHandling() {
  const [draggedItem, setDraggedItem] = useState(null);
  const [dropZoneActive, setDropZoneActive] = useState(false);
  
  // Drag and Drop
  const handleDragStart = (event, item) => {
    setDraggedItem(item);
    event.dataTransfer.effectAllowed = "move";
  };
  
  const handleDragOver = (event) => {
    event.preventDefault();
    setDropZoneActive(true);
  };
  
  const handleDragLeave = () => {
    setDropZoneActive(false);
  };
  
  const handleDrop = (event) => {
    event.preventDefault();
    setDropZoneActive(false);
    
    if (draggedItem) {
      console.log("Dropped item:", draggedItem);
      setDraggedItem(null);
    }
  };
  
  // Keyboard navigation
  const handleKeyDown = (event) => {
    switch (event.key) {
      case "ArrowUp":
        event.preventDefault();
        console.log("Navigate up");
        break;
      case "ArrowDown":
        event.preventDefault();
        console.log("Navigate down");
        break;
      case "Enter":
        console.log("Select item");
        break;
      case "Escape":
        console.log("Cancel action");
        break;
      default:
        break;
    }
  };
  
  const items = ["Item 1", "Item 2", "Item 3"];
  
  return (
    <div>
      <h3>Draggable Items</h3>
      {items.map((item, index) => (
        <div
          key={index}
          draggable
          onDragStart={(e) => handleDragStart(e, item)}
          style={{
            padding: "10px",
            margin: "5px",
            backgroundColor: "#f0f0f0",
            cursor: "move"
          }}
        >
          {item}
        </div>
      ))}
      
      <div
        onDragOver={handleDragOver}
        onDragLeave={handleDragLeave}
        onDrop={handleDrop}
        style={{
          width: "200px",
          height: "100px",
          border: "2px dashed #ccc",
          backgroundColor: dropZoneActive ? "#e6f3ff" : "transparent",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          marginTop: "20px"
        }}
      >
        Drop Zone
      </div>
      
      <div
        tabIndex="0"
        onKeyDown={handleKeyDown}
        style={{
          padding: "20px",
          border: "1px solid #ccc",
          marginTop: "20px",
          outline: "none"
        }}
      >
        Focus me and use arrow keys, Enter, or Escape
      </div>
    </div>
  );
}

Best Practices for Event Handling and Forms

  • Use controlled components: For better control and validation
  • Validate on both client and server: Never trust client-side validation alone
  • Provide immediate feedback: Show errors as users type
  • Use semantic HTML: Proper form elements and labels
  • Handle loading states: Show feedback during form submission
  • Prevent multiple submissions: Disable submit button during processing
  • Use event delegation: For dynamic content
  • Clean up event listeners: In useEffect cleanup (covered later)

Summary

Event handling and forms are essential for creating interactive React applications. You've learned about SyntheticEvents, controlled vs uncontrolled components, form validation, and advanced event handling patterns. These concepts form the foundation for building rich, interactive user interfaces. In the next lesson, we'll explore React Hooks in more depth and learn about component lifecycle management.