Basic State Management in Client Components¶
The useState hook, which we just used in the Counter component, is the most fundamental way to manage state within a functional React component. State allows your components to "remember" data and re-render when that data changes, creating dynamic user interfaces.
useState Hook¶
- Purpose: To add state to functional components.
- Syntax:
const [stateVariable, setStateFunction] = useState(initialValue);stateVariable: The current value of the state.setStateFunction: A function you call to update thestateVariable. When you call this function, React will re-render the component with the new state.initialValue: The initial value of the state when the component first renders.
useEffect Hook¶
- Purpose: To perform side effects in functional components. Side effects are operations that interact with the outside world, like data fetching, subscriptions, or manually changing the DOM.
- Syntax:
useEffect(() => { /* side effect code */ }, [dependencies]);- The first argument is a function that contains your effect logic.
- The second argument (optional) is a dependency array.
- If the array is empty (
[]), the effect runs only once after the initial render (likecomponentDidMount). - If dependencies are listed (e.g.,
[count]), the effect runs when any of those dependencies change. - If omitted, the effect runs after every render.
- If the array is empty (
- Cleanup: The effect function can optionally return a cleanup function, which runs before the component unmounts or before the effect runs again (if dependencies change).
Managing Tasks within the Component's State (Initially In-Memory Array)¶
Before we connect to a database, let's manage a list of tasks purely within the client-side state using useState. This will give us a working model of task addition, display, and completion before introducing persistence.
We'll create a new Client Component specifically for our Task List management.
-
Create a new file
components/TaskList.tsx:components/TaskList.tsx// components/TaskList.tsx // This Client Component manages and displays a list of tasks using in-memory state. 'use client'; import { useState, useEffect } from 'react'; // Define a TypeScript interface for our Task object for better type safety interface Task { id: string; title: string; completed: boolean; } export default function TaskList() { // Initialize tasks state as an empty array of Task objects const [tasks, setTasks] = useState<Task[]>([]); const [newTaskTitle, setNewTaskTitle] = useState<string>(''); // State for new task input // Simulate fetching tasks on initial load (optional, for demonstration) useEffect(() => { // In a real app, this would be a fetch call to an API 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); }, []); // Empty dependency array means this effect runs only once after the initial render // Function to add a new task const addTask = () => { if (newTaskTitle.trim() === '') { // In a real app, use a custom modal for user feedback, not alert console.log('Task title cannot be empty.'); return; } const newTask: Task = { id: String(tasks.length + 1), // Simple unique ID for now title: newTaskTitle, completed: false, }; setTasks([...tasks, newTask]); // Add new task to the existing array setNewTaskTitle(''); // Clear the input field }; // Function to toggle task completion status const toggleTaskCompletion = (id: string) => { setTasks( tasks.map((task) => task.id === id ? { ...task, completed: !task.completed } : task ) ); }; // Function to delete a 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 */} <div 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 onClick={addTask} 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> </div> {/* 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> ); } -
Integrate
TaskList.tsxintoapp/dashboard/page.tsx: Now, let's replace the placeholder content in our dashboard page with our new interactiveTaskList.
app/dashboard/page.tsx (Update this file)
// app/dashboard/page.tsx
// This file defines the content for the /dashboard route.
// Updated to include the TaskList Client Component for in-memory task management.
import TaskList from '../../components/TaskList'; // Import the TaskList component
export default function DashboardPage() {
return (
<div className="flex flex-col items-center p-4">
<h2 className="text-3xl font-bold text-gray-800 mb-6">Your Dashboard</h2>
<p className="text-lg text-gray-600 mb-8">
Manage your tasks efficiently right from here.
</p>
{/* Add the TaskList component */}
<TaskList />
</div>
);
}
Summary¶
What we've done:
- We've created a
TaskList.tsxcomponent that usesuseStateto manage an array of tasks. - We implemented functions to
addTask,toggleTaskCompletion, anddeleteTask, all of which update the component's state. - We've used
useEffectto simulate an initial task load when the component mounts. - We integrated this
TaskListcomponent into our/dashboardpage.
To test the in-memory task management:
- Ensure your development server is running (
npm run dev). - Navigate to
http://localhost:3000/dashboard. - Try adding new tasks using the input field, marking tasks as complete/incomplete using the checkboxes, and deleting tasks. Observe how the UI updates immediately.