Skip to content

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 the stateVariable. 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 (like componentDidMount).
      • If dependencies are listed (e.g., [count]), the effect runs when any of those dependencies change.
      • If omitted, the effect runs after every render.
  • 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.

  1. 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>
      );
    }
    
  2. Integrate TaskList.tsx into app/dashboard/page.tsx: Now, let's replace the placeholder content in our dashboard page with our new interactive TaskList.

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.tsx component that uses useState to manage an array of tasks.
  • We implemented functions to addTask, toggleTaskCompletion, and deleteTask, all of which update the component's state.
  • We've used useEffect to simulate an initial task load when the component mounts.
  • We integrated this TaskList component into our /dashboard page.

To test the in-memory task management:

  1. Ensure your development server is running (npm run dev).
  2. Navigate to http://localhost:3000/dashboard.
  3. 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.