Form Basics in React¶
Forms are essential for collecting user input. In React, the most common way to handle form inputs is through controlled components. A controlled component is an input element whose value is controlled by React state.
Controlled Components Pattern¶
With controlled components:
- The value of the input element is stored in a React state variable.
- The
onChangeevent listener on the input updates this state variable whenever the input value changes. - The input element's
valueprop is explicitly set to the state variable.
This creates a "single source of truth" for the input's value, making it easy to validate, manipulate, and submit the data.
Handling Input Changes (onChange)¶
The onChange event handler is crucial for controlled inputs. It fires every time the value of the input element changes (e.g., when a user types a character). Inside the handler, you'll call the state setter function to update the state with the new input value.
Example from TaskList.tsx for newTaskTitle:
<input
type="text"
placeholder="Add a new task..."
value={newTaskTitle} // Value is controlled by state
onChange={(e) => setNewTaskTitle(e.target.value)} // Update state on change
// ... other props
/>
Handling Form Submission (onSubmit)¶
When a user submits a form (e.g., by clicking a submit button or pressing Enter in an input field), the onSubmit event handler on the <form> element is triggered.
It's very important to call event.preventDefault() inside the onSubmit handler. This stops the browser's default behavior of reloading the page, which is typically undesirable in a single-page application (or a Next.js application that handles routing itself).
Our TaskList.tsx already uses this concept for the "Add Task" functionality, though it's attached to a button click rather than a full <form> element. Let's wrap the input and button in a <form> element to demonstrate onSubmit.
-
Update
components/TaskList.tsxto use a<form>tag:components/TaskList.tsx(Update this file)// components/TaskList.tsx // Updated to use a <form> element with onSubmit for task input. 'use client'; import { useState, useEffect, FormEvent } from 'react'; // Import FormEvent for type safety interface Task { id: string; title: string; completed: boolean; } export default function TaskList() { const [tasks, setTasks] = useState<Task[]>([]); const [newTaskTitle, setNewTaskTitle] = useState<string>(''); useEffect(() => { const initialTasks: Task[] = [ { id: '1', title: 'Learn Next.js Routing', completed: true }, { id: '2', title: 'Build Task Input Form', completed: false }, { id: '3', title: 'Implement Task Completion', completed: false }, ]; setTasks(initialTasks); }, []); // Modified addTask function to handle form submission event const addTask = (event: FormEvent) => { event.preventDefault(); // Prevent default form submission behavior (page reload) if (newTaskTitle.trim() === '') { console.log('Task title cannot be empty.'); return; } const newTask: Task = { id: crypto.randomUUID(), // Use a more robust unique ID for new tasks title: newTaskTitle, completed: false, }; setTasks([...tasks, newTask]); setNewTaskTitle(''); }; const toggleTaskCompletion = (id: string) => { setTasks( tasks.map((task) => task.id === id ? { ...task, completed: !task.completed } : task ) ); }; const deleteTask = (id: string) => { setTasks(tasks.filter((task) => task.id !== id)); }; return ( <div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-xl border border-gray-200"> <h3 className="text-3xl font-bold text-gray-800 mb-6">Your Tasks</h3> {/* Task Input Form - now a proper <form> element */} <form onSubmit={addTask} className="flex flex-col md:flex-row gap-4 mb-8"> <input type="text" placeholder="Add a new task..." value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} className="flex-grow p-3 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 outline-none" /> <button type="submit" // Set type to submit for form submission className="px-6 py-3 bg-blue-600 text-white rounded-md shadow-md hover:bg-blue-700 transition duration-300 transform hover:scale-105" > Add Task </button> </form> {/* Task List */} {tasks.length === 0 ? ( <p className="text-center text-gray-500 italic">No tasks yet. Start adding some!</p> ) : ( <ul className="space-y-4"> {tasks.map((task) => ( <li key={task.id} className="flex items-center justify-between p-4 bg-gray-50 border border-gray-200 rounded-md shadow-sm transition duration-200 hover:bg-gray-100" > <div className="flex items-center"> <input type="checkbox" checked={task.completed} onChange={() => toggleTaskCompletion(task.id)} className="form-checkbox h-5 w-5 text-blue-600 rounded mr-3 cursor-pointer" /> <span className={`text-lg font-medium ${ task.completed ? 'line-through text-gray-500' : 'text-gray-900' }`} > {task.title} </span> </div> <button onClick={() => deleteTask(task.id)} className="ml-4 px-3 py-1 bg-red-500 text-white rounded-md text-sm shadow-sm hover:bg-red-600 transition duration-300" > Delete </button> </li> ))} </ul> )} </div> ); }
Summary¶
What we've done:
- We've wrapped the task input and button in a
<form>tag. - We've added an
onSubmithandler to the form, which callsaddTaskandevent.preventDefault(). - We've changed the button's
typetosubmit. - We updated the
idgeneration to usecrypto.randomUUID()for more robust unique IDs.
To verify form submission:
- Ensure your development server is running (
npm run dev). - Navigate to
http://localhost:3000/dashboard. - Type a task and press
Enter. The task should be added without a page reload. You can also click the "Add Task" button.
This module has provided you with a solid understanding of Client Components, basic state management with useState and useEffect, and how to handle forms in React. This is the foundation for creating interactive UIs that respond to user input. In the next module, we will explore more advanced data fetching techniques using Server Components and learn about different rendering strategies in Next.js.