Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /React
  4. /Building Complete App
Your Progress0%
0 of 42 completed

React Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App

Building a Complete App

Creating a real-world multi-page application

You've learned all the React fundamentals - now it's time to put everything together! In this final lesson, you'll build a complete Task Management Application that demonstrates real-world patterns and best practices. You'll use routing, state management, API integration, forms, error handling, and testing. This is your capstone project - let's build something amazing! šŸŽÆ

Project Overview: Task Manager App

We'll build a multi-page task management application with these features:

  • Authentication - Login/logout functionality
  • Dashboard - Overview of tasks and statistics
  • Task List - View, filter, and search tasks
  • Task Details - View and edit individual tasks
  • Create Task - Form with validation
  • Categories - Organize tasks by category

Technologies Used

  • React 18 with hooks
  • React Router for navigation
  • Context API for state management
  • localStorage for data persistence
  • Custom hooks for reusable logic
  • Error boundaries for error handling
  • Jest and React Testing Library for tests

Project Structure

project-structure.txt
task-manager/
ā”œā”€ā”€ public/
│   └── index.html
ā”œā”€ā”€ src/
│   ā”œā”€ā”€ components/
│   │   ā”œā”€ā”€ ui/
│   │   │   ā”œā”€ā”€ Button.jsx
│   │   │   ā”œā”€ā”€ Input.jsx
│   │   │   ā”œā”€ā”€ Card.jsx
│   │   │   └── Modal.jsx
│   │   ā”œā”€ā”€ layout/
│   │   │   ā”œā”€ā”€ Header.jsx
│   │   │   ā”œā”€ā”€ Sidebar.jsx
│   │   │   └── Layout.jsx
│   │   └── tasks/
│   │       ā”œā”€ā”€ TaskCard.jsx
│   │       ā”œā”€ā”€ TaskList.jsx
│   │       ā”œā”€ā”€ TaskFilter.jsx
│   │       └── TaskForm.jsx
│   │
│   ā”œā”€ā”€ pages/
│   │   ā”œā”€ā”€ LoginPage.jsx
│   │   ā”œā”€ā”€ DashboardPage.jsx
│   │   ā”œā”€ā”€ TasksPage.jsx
│   │   ā”œā”€ā”€ TaskDetailPage.jsx
│   │   └── NotFoundPage.jsx
│   │
│   ā”œā”€ā”€ context/
│   │   ā”œā”€ā”€ AuthContext.jsx
│   │   └── TaskContext.jsx
│   │
│   ā”œā”€ā”€ hooks/
│   │   ā”œā”€ā”€ useAuth.js
│   │   ā”œā”€ā”€ useTasks.js
│   │   └── useLocalStorage.js
│   │
│   ā”œā”€ā”€ utils/
│   │   ā”œā”€ā”€ api.js
│   │   ā”œā”€ā”€ validation.js
│   │   └── helpers.js
│   │
│   ā”œā”€ā”€ App.jsx
│   ā”œā”€ā”€ index.js
│   └── index.css
│
ā”œā”€ā”€ package.json
└── README.md

Step 1: Authentication Context

context/AuthContext.jsx
import React, { createContext, useContext, useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'

const AuthContext = createContext()

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)
  
  // Load user from localStorage on mount
  useEffect(() => {
    const savedUser = localStorage.getItem('user')
    if (savedUser) {
      setUser(JSON.parse(savedUser))
    }
    setLoading(false)
  }, [])
  
  const login = (email, password) => {
    // Simulate API call
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (email && password) {
          const user = {
            id: '1',
            email,
            name: email.split('@')[0]
          }
          setUser(user)
          localStorage.setItem('user', JSON.stringify(user))
          resolve(user)
        } else {
          reject(new Error('Invalid credentials'))
        }
      }, 500)
    })
  }
  
  const logout = () => {
    setUser(null)
    localStorage.removeItem('user')
  }
  
  const value = {
    user,
    loading,
    login,
    logout,
    isAuthenticated: !!user
  }
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  )
}

export function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider')
  }
  return context
}

Step 2: Task Context

context/TaskContext.jsx
import React, { createContext, useContext, useState, useEffect } from 'react'

const TaskContext = createContext()

export function TaskProvider({ children }) {
  const [tasks, setTasks] = useState([])
  const [categories, setCategories] = useState(['Work', 'Personal', 'Shopping'])
  
  // Load tasks from localStorage
  useEffect(() => {
    const savedTasks = localStorage.getItem('tasks')
    if (savedTasks) {
      setTasks(JSON.parse(savedTasks))
    } else {
      // Initial demo data
      setTasks([
        {
          id: '1',
          title: 'Complete React tutorial',
          description: 'Finish all 42 lessons',
          category: 'Work',
          priority: 'high',
          completed: false,
          dueDate: '2024-12-31',
          createdAt: new Date().toISOString()
        },
        {
          id: '2',
          title: 'Buy groceries',
          description: 'Milk, eggs, bread',
          category: 'Shopping',
          priority: 'medium',
          completed: false,
          dueDate: '2024-12-25',
          createdAt: new Date().toISOString()
        }
      ])
    }
  }, [])
  
  // Save tasks to localStorage whenever they change
  useEffect(() => {
    localStorage.setItem('tasks', JSON.stringify(tasks))
  }, [tasks])
  
  const addTask = (task) => {
    const newTask = {
      ...task,
      id: Date.now().toString(),
      completed: false,
      createdAt: new Date().toISOString()
    }
    setTasks(prev => [newTask, ...prev])
    return newTask
  }
  
  const updateTask = (id, updates) => {
    setTasks(prev => 
      prev.map(task => 
        task.id === id ? { ...task, ...updates } : task
      )
    )
  }
  
  const deleteTask = (id) => {
    setTasks(prev => prev.filter(task => task.id !== id))
  }
  
  const toggleComplete = (id) => {
    setTasks(prev =>
      prev.map(task =>
        task.id === id ? { ...task, completed: !task.completed } : task
      )
    )
  }
  
  const getTasksByCategory = (category) => {
    if (!category || category === 'all') return tasks
    return tasks.filter(task => task.category === category)
  }
  
  const getTaskStats = () => {
    return {
      total: tasks.length,
      completed: tasks.filter(t => t.completed).length,
      pending: tasks.filter(t => !t.completed).length,
      highPriority: tasks.filter(t => t.priority === 'high' && !t.completed).length
    }
  }
  
  const value = {
    tasks,
    categories,
    addTask,
    updateTask,
    deleteTask,
    toggleComplete,
    getTasksByCategory,
    getTaskStats
  }
  
  return (
    <TaskContext.Provider value={value}>
      {children}
    </TaskContext.Provider>
  )
}

export function useTasks() {
  const context = useContext(TaskContext)
  if (!context) {
    throw new Error('useTasks must be used within TaskProvider')
  }
  return context
}

Step 3: Routing Setup

App.jsx
import React from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider, useAuth } from './context/AuthContext'
import { TaskProvider } from './context/TaskContext'
import Layout from './components/layout/Layout'
import LoginPage from './pages/LoginPage'
import DashboardPage from './pages/DashboardPage'
import TasksPage from './pages/TasksPage'
import TaskDetailPage from './pages/TaskDetailPage'
import NotFoundPage from './pages/NotFoundPage'

// Protected Route component
function ProtectedRoute({ children }) {
  const { isAuthenticated, loading } = useAuth()
  
  if (loading) {
    return <div className="loading">Loading...</div>
  }
  
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />
  }
  
  return children
}

function App() {
  return (
    <BrowserRouter>
      <AuthProvider>
        <TaskProvider>
          <Routes>
            {/* Public route */}
            <Route path="/login" element={<LoginPage />} />
            
            {/* Protected routes */}
            <Route
              path="/"
              element={
                <ProtectedRoute>
                  <Layout />
                </ProtectedRoute>
              }
            >
              <Route index element={<DashboardPage />} />
              <Route path="tasks" element={<TasksPage />} />
              <Route path="tasks/:id" element={<TaskDetailPage />} />
            </Route>
            
            {/* 404 */}
            <Route path="*" element={<NotFoundPage />} />
          </Routes>
        </TaskProvider>
      </AuthProvider>
    </BrowserRouter>
  )
}

export default App

Step 4: Layout Components

Main Layout

components/layout/Layout.jsx
import React from 'react'
import { Outlet } from 'react-router-dom'
import Header from './Header'
import Sidebar from './Sidebar'

function Layout() {
  return (
    <div className="app-layout">
      <Header />
      <div className="layout-container">
        <Sidebar />
        <main className="main-content">
          <Outlet />
        </main>
      </div>
    </div>
  )
}

export default Layout

Header Component

components/layout/Header.jsx
import React from 'react'
import { useAuth } from '../../context/AuthContext'
import { useNavigate } from 'react-router-dom'

function Header() {
  const { user, logout } = useAuth()
  const navigate = useNavigate()
  
  const handleLogout = () => {
    logout()
    navigate('/login')
  }
  
  return (
    <header className="header">
      <div className="header-content">
        <h1 className="logo">Task Manager</h1>
        
        <div className="user-menu">
          <span className="user-name">Welcome, {user?.name}</span>
          <button onClick={handleLogout} className="btn-logout">
            Logout
          </button>
        </div>
      </div>
    </header>
  )
}

export default Header

Sidebar Navigation

components/layout/Sidebar.jsx
import React from 'react'
import { NavLink } from 'react-router-dom'

function Sidebar() {
  return (
    <aside className="sidebar">
      <nav className="sidebar-nav">
        <NavLink to="/" end className="nav-link">
          šŸ“Š Dashboard
        </NavLink>
        <NavLink to="/tasks" className="nav-link">
          āœ“ All Tasks
        </NavLink>
      </nav>
    </aside>
  )
}

export default Sidebar

Step 5: Page Components

Login Page

pages/LoginPage.jsx
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'

function LoginPage() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(false)
  const { login } = useAuth()
  const navigate = useNavigate()
  
  const handleSubmit = async (e) => {
    e.preventDefault()
    setError(null)
    setLoading(true)
    
    try {
      await login(email, password)
      navigate('/')
    } catch (err) {
      setError(err.message)
    } finally {
      setLoading(false)
    }
  }
  
  return (
    <div className="login-page">
      <div className="login-card">
        <h1>Task Manager</h1>
        <h2>Login</h2>
        
        {error && (
          <div className="error-message">{error}</div>
        )}
        
        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label htmlFor="email">Email</label>
            <input
              id="email"
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
              placeholder="Enter your email"
            />
          </div>
          
          <div className="form-group">
            <label htmlFor="password">Password</label>
            <input
              id="password"
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
              placeholder="Enter your password"
            />
          </div>
          
          <button type="submit" disabled={loading} className="btn-primary">
            {loading ? 'Logging in...' : 'Login'}
          </button>
        </form>
        
        <p className="demo-note">
          Demo: Use any email and password to login
        </p>
      </div>
    </div>
  )
}

export default LoginPage

Dashboard Page

pages/DashboardPage.jsx
import React from 'react'
import { Link } from 'react-router-dom'
import { useTasks } from '../context/TaskContext'

function DashboardPage() {
  const { tasks, getTaskStats } = useTasks()
  const stats = getTaskStats()
  
  const recentTasks = tasks.slice(0, 5)
  
  return (
    <div className="dashboard-page">
      <h1>Dashboard</h1>
      
      {/* Stats Cards */}
      <div className="stats-grid">
        <div className="stat-card">
          <h3>Total Tasks</h3>
          <p className="stat-number">{stats.total}</p>
        </div>
        
        <div className="stat-card">
          <h3>Completed</h3>
          <p className="stat-number success">{stats.completed}</p>
        </div>
        
        <div className="stat-card">
          <h3>Pending</h3>
          <p className="stat-number warning">{stats.pending}</p>
        </div>
        
        <div className="stat-card">
          <h3>High Priority</h3>
          <p className="stat-number danger">{stats.highPriority}</p>
        </div>
      </div>
      
      {/* Recent Tasks */}
      <div className="section">
        <div className="section-header">
          <h2>Recent Tasks</h2>
          <Link to="/tasks" className="btn-link">View All</Link>
        </div>
        
        <div className="task-list">
          {recentTasks.length === 0 ? (
            <p className="empty-state">No tasks yet. Create your first task!</p>
          ) : (
            recentTasks.map(task => (
              <Link 
                key={task.id} 
                to={`/tasks/${task.id}`}
                className="task-item"
              >
                <div className="task-info">
                  <h3>{task.title}</h3>
                  <p>{task.category}</p>
                </div>
                <span className={`priority-badge ${task.priority}`}>
                  {task.priority}
                </span>
              </Link>
            ))
          )}
        </div>
      </div>
    </div>
  )
}

export default DashboardPage

Step 6: Task Components

Tasks Page with Filter

pages/TasksPage.jsx
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import { useTasks } from '../context/TaskContext'
import TaskForm from '../components/tasks/TaskForm'

function TasksPage() {
  const { tasks, categories, toggleComplete, deleteTask } = useTasks()
  const [filter, setFilter] = useState('all')
  const [search, setSearch] = useState('')
  const [showForm, setShowForm] = useState(false)
  
  // Filter and search tasks
  const filteredTasks = tasks.filter(task => {
    const matchesCategory = filter === 'all' || task.category === filter
    const matchesSearch = task.title.toLowerCase().includes(search.toLowerCase()) ||
                         task.description.toLowerCase().includes(search.toLowerCase())
    return matchesCategory && matchesSearch
  })
  
  return (
    <div className="tasks-page">
      <div className="page-header">
        <h1>All Tasks</h1>
        <button 
          onClick={() => setShowForm(true)}
          className="btn-primary"
        >
          + New Task
        </button>
      </div>
      
      {/* Filters */}
      <div className="filters">
        <input
          type="text"
          placeholder="Search tasks..."
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          className="search-input"
        />
        
        <select 
          value={filter} 
          onChange={(e) => setFilter(e.target.value)}
          className="filter-select"
        >
          <option value="all">All Categories</option>
          {categories.map(cat => (
            <option key={cat} value={cat}>{cat}</option>
          ))}
        </select>
      </div>
      
      {/* Task List */}
      <div className="tasks-grid">
        {filteredTasks.length === 0 ? (
          <p className="empty-state">
            {search ? 'No tasks match your search.' : 'No tasks found. Create one!'}
          </p>
        ) : (
          filteredTasks.map(task => (
            <div 
              key={task.id} 
              className={`task-card ${task.completed ? 'completed' : ''}`}
            >
              <div className="task-card-header">
                <input
                  type="checkbox"
                  checked={task.completed}
                  onChange={() => toggleComplete(task.id)}
                />
                <Link to={`/tasks/${task.id}`} className="task-title">
                  {task.title}
                </Link>
              </div>
              
              <p className="task-description">{task.description}</p>
              
              <div className="task-meta">
                <span className={`badge ${task.category.toLowerCase()}`}>
                  {task.category}
                </span>
                <span className={`priority ${task.priority}`}>
                  {task.priority}
                </span>
              </div>
              
              <div className="task-actions">
                <Link to={`/tasks/${task.id}`} className="btn-secondary">
                  View
                </Link>
                <button 
                  onClick={() => deleteTask(task.id)}
                  className="btn-danger"
                >
                  Delete
                </button>
              </div>
            </div>
          ))
        )}
      </div>
      
      {/* Task Form Modal */}
      {showForm && (
        <TaskForm onClose={() => setShowForm(false)} />
      )}
    </div>
  )
}

export default TasksPage

Task Form Component

components/tasks/TaskForm.jsx
import React, { useState } from 'react'
import { useTasks } from '../../context/TaskContext'

function TaskForm({ onClose, task }) {
  const { addTask, updateTask, categories } = useTasks()
  const [formData, setFormData] = useState({
    title: task?.title || '',
    description: task?.description || '',
    category: task?.category || categories[0],
    priority: task?.priority || 'medium',
    dueDate: task?.dueDate || ''
  })
  const [errors, setErrors] = useState({})
  
  const validate = () => {
    const newErrors = {}
    
    if (!formData.title.trim()) {
      newErrors.title = 'Title is required'
    }
    
    if (formData.title.length > 100) {
      newErrors.title = 'Title must be less than 100 characters'
    }
    
    if (!formData.description.trim()) {
      newErrors.description = 'Description is required'
    }
    
    return newErrors
  }
  
  const handleSubmit = (e) => {
    e.preventDefault()
    
    const newErrors = validate()
    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors)
      return
    }
    
    if (task) {
      updateTask(task.id, formData)
    } else {
      addTask(formData)
    }
    
    onClose()
  }
  
  const handleChange = (e) => {
    const { name, value } = e.target
    setFormData(prev => ({ ...prev, [name]: value }))
    // Clear error when user types
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }))
    }
  }
  
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h2>{task ? 'Edit Task' : 'New Task'}</h2>
          <button onClick={onClose} className="btn-close">Ɨ</button>
        </div>
        
        <form onSubmit={handleSubmit} className="task-form">
          <div className="form-group">
            <label htmlFor="title">Title *</label>
            <input
              id="title"
              name="title"
              type="text"
              value={formData.title}
              onChange={handleChange}
              className={errors.title ? 'error' : ''}
            />
            {errors.title && <span className="error-text">{errors.title}</span>}
          </div>
          
          <div className="form-group">
            <label htmlFor="description">Description *</label>
            <textarea
              id="description"
              name="description"
              value={formData.description}
              onChange={handleChange}
              rows="4"
              className={errors.description ? 'error' : ''}
            />
            {errors.description && (
              <span className="error-text">{errors.description}</span>
            )}
          </div>
          
          <div className="form-row">
            <div className="form-group">
              <label htmlFor="category">Category</label>
              <select
                id="category"
                name="category"
                value={formData.category}
                onChange={handleChange}
              >
                {categories.map(cat => (
                  <option key={cat} value={cat}>{cat}</option>
                ))}
              </select>
            </div>
            
            <div className="form-group">
              <label htmlFor="priority">Priority</label>
              <select
                id="priority"
                name="priority"
                value={formData.priority}
                onChange={handleChange}
              >
                <option value="low">Low</option>
                <option value="medium">Medium</option>
                <option value="high">High</option>
              </select>
            </div>
          </div>
          
          <div className="form-group">
            <label htmlFor="dueDate">Due Date</label>
            <input
              id="dueDate"
              name="dueDate"
              type="date"
              value={formData.dueDate}
              onChange={handleChange}
            />
          </div>
          
          <div className="form-actions">
            <button type="button" onClick={onClose} className="btn-secondary">
              Cancel
            </button>
            <button type="submit" className="btn-primary">
              {task ? 'Update' : 'Create'} Task
            </button>
          </div>
        </form>
      </div>
    </div>
  )
}

export default TaskForm

Step 7: Basic Styling

index.css
/* Reset and Base Styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  line-height: 1.6;
  color: #333;
  background: #f5f5f5;
}

/* Layout */
.app-layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.header {
  background: #2563eb;
  color: white;
  padding: 1rem 2rem;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.header-content {
  max-width: 1400px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.logo {
  font-size: 1.5rem;
  font-weight: bold;
}

.user-menu {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.layout-container {
  display: flex;
  flex: 1;
  max-width: 1400px;
  margin: 0 auto;
  width: 100%;
}

.sidebar {
  width: 250px;
  background: white;
  border-right: 1px solid #e5e7eb;
  padding: 2rem 1rem;
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.nav-link {
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
  text-decoration: none;
  color: #4b5563;
  transition: all 0.2s;
}

.nav-link:hover {
  background: #f3f4f6;
}

.nav-link.active {
  background: #eff6ff;
  color: #2563eb;
  font-weight: 600;
}

.main-content {
  flex: 1;
  padding: 2rem;
}

/* Buttons */
.btn-primary {
  background: #2563eb;
  color: white;
  border: none;
  padding: 0.75rem 1.5rem;
  border-radius: 0.5rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
}

.btn-primary:hover {
  background: #1d4ed8;
}

.btn-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-secondary {
  background: white;
  color: #4b5563;
  border: 1px solid #d1d5db;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  font-weight: 500;
  cursor: pointer;
}

.btn-danger {
  background: #ef4444;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  cursor: pointer;
}

/* Cards */
.stat-card {
  background: white;
  padding: 1.5rem;
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.stat-number {
  font-size: 2rem;
  font-weight: bold;
  margin-top: 0.5rem;
}

.task-card {
  background: white;
  padding: 1.5rem;
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  transition: transform 0.2s;
}

.task-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}

.task-card.completed {
  opacity: 0.6;
}

/* Forms */
.form-group {
  margin-bottom: 1rem;
}

.form-group label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
  color: #374151;
}

.form-group input,
.form-group textarea,
.form-group select {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: 0.375rem;
  font-size: 1rem;
}

.form-group input.error,
.form-group textarea.error {
  border-color: #ef4444;
}

.error-text {
  color: #ef4444;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

/* Modal */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal {
  background: white;
  border-radius: 0.5rem;
  max-width: 600px;
  width: 90%;
  max-height: 90vh;
  overflow-y: auto;
}

/* Responsive */
@media (max-width: 768px) {
  .layout-container {
    flex-direction: column;
  }
  
  .sidebar {
    width: 100%;
    border-right: none;
    border-bottom: 1px solid #e5e7eb;
  }
  
  .stats-grid {
    grid-template-columns: 1fr;
  }
}

Step 8: Testing the App

__tests__/TaskContext.test.js
import { renderHook, act } from '@testing-library/react'
import { TaskProvider, useTasks } from '../context/TaskContext'

const wrapper = ({ children }) => <TaskProvider>{children}</TaskProvider>

describe('TaskContext', () => {
  test('adds a new task', () => {
    const { result } = renderHook(() => useTasks(), { wrapper })
    
    const newTask = {
      title: 'Test Task',
      description: 'Test Description',
      category: 'Work',
      priority: 'high'
    }
    
    act(() => {
      result.current.addTask(newTask)
    })
    
    expect(result.current.tasks).toHaveLength(3) // 2 initial + 1 new
    expect(result.current.tasks[0].title).toBe('Test Task')
  })
  
  test('toggles task completion', () => {
    const { result } = renderHook(() => useTasks(), { wrapper })
    
    const taskId = result.current.tasks[0].id
    const initialStatus = result.current.tasks[0].completed
    
    act(() => {
      result.current.toggleComplete(taskId)
    })
    
    expect(result.current.tasks[0].completed).toBe(!initialStatus)
  })
  
  test('calculates task stats correctly', () => {
    const { result } = renderHook(() => useTasks(), { wrapper })
    
    const stats = result.current.getTaskStats()
    
    expect(stats).toHaveProperty('total')
    expect(stats).toHaveProperty('completed')
    expect(stats).toHaveProperty('pending')
    expect(stats).toHaveProperty('highPriority')
  })
})
__tests__/TaskForm.test.jsx
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { TaskProvider } from '../context/TaskContext'
import TaskForm from '../components/tasks/TaskForm'

const mockOnClose = jest.fn()

const renderForm = () => {
  return render(
    <TaskProvider>
      <TaskForm onClose={mockOnClose} />
    </TaskProvider>
  )
}

describe('TaskForm', () => {
  test('renders form fields', () => {
    renderForm()
    
    expect(screen.getByLabelText(/title/i)).toBeInTheDocument()
    expect(screen.getByLabelText(/description/i)).toBeInTheDocument()
    expect(screen.getByLabelText(/category/i)).toBeInTheDocument()
    expect(screen.getByLabelText(/priority/i)).toBeInTheDocument()
  })
  
  test('shows validation errors for empty fields', async () => {
    renderForm()
    
    const submitButton = screen.getByRole('button', { name: /create task/i })
    await userEvent.click(submitButton)
    
    expect(await screen.findByText(/title is required/i)).toBeInTheDocument()
    expect(await screen.findByText(/description is required/i)).toBeInTheDocument()
  })
  
  test('creates task with valid data', async () => {
    renderForm()
    
    await userEvent.type(screen.getByLabelText(/title/i), 'New Task')
    await userEvent.type(screen.getByLabelText(/description/i), 'Task description')
    
    const submitButton = screen.getByRole('button', { name: /create task/i })
    await userEvent.click(submitButton)
    
    await waitFor(() => {
      expect(mockOnClose).toHaveBeenCalled()
    })
  })
})

Possible Enhancements

This is a solid foundation. Here are ideas to extend the project:

  • Backend Integration
    • Replace localStorage with real API
    • User registration and authentication
    • Data synchronization across devices
  • Advanced Features
    • Task tags and labels
    • Subtasks and checklists
    • Task comments and attachments
    • Task assignment to team members
    • Recurring tasks
  • UI Improvements
    • Dark mode toggle
    • Drag and drop task reordering
    • Calendar view
    • Task statistics charts
    • Animations and transitions
  • Performance
    • Virtual scrolling for large task lists
    • Lazy loading of routes
    • Service worker for offline support
    • Optimistic UI updates
  • Testing
    • E2E tests with Cypress or Playwright
    • Visual regression testing
    • Accessibility testing

Key Learnings from This Project

You've demonstrated:

  • āœ“ Project structure and organization
  • āœ“ Context API for state management
  • āœ“ React Router for navigation
  • āœ“ Protected routes with authentication
  • āœ“ Form handling with validation
  • āœ“ CRUD operations (Create, Read, Update, Delete)
  • āœ“ Search and filter functionality
  • āœ“ localStorage for data persistence
  • āœ“ Custom hooks for reusable logic
  • āœ“ Component composition and reusability
  • āœ“ Responsive layout design
  • āœ“ Modal dialogs and overlays
  • āœ“ Component testing

Congratulations! šŸŽ‰

You've completed all 42 React tutorials and built a complete application!

Throughout this series, you've mastered:

  • JSX Fundamentals - Syntax, expressions, components
  • Components Basics - Props, composition, reusability
  • State Management - useState, Context API
  • Events and Interactivity - Handlers, forms, lists
  • Side Effects and Data - useEffect, data fetching
  • Advanced Hooks - useRef, useContext, useMemo, useCallback
  • React Patterns - Custom hooks, HOCs, render props
  • React Router - Navigation, dynamic routes
  • Best Practices - Organization, performance, errors, testing
  • Real-World Project - Complete application from scratch

You're now ready to:

  • āœ“ Build production-ready React applications
  • āœ“ Write clean, maintainable, testable code
  • āœ“ Handle complex state and routing
  • āœ“ Optimize for performance
  • āœ“ Debug and troubleshoot issues
  • āœ“ Continue learning advanced topics
  • āœ“ Contribute to React projects
  • āœ“ Teach others what you've learned

Your React Journey Continues

This is not the end - it's just the beginning! Continue your React journey:

Advanced Topics to Explore

  • TypeScript with React - Type safety for larger apps
  • State Management Libraries - Redux Toolkit, Zustand, Jotai
  • Server-Side Rendering - Next.js, Remix
  • React Query - Advanced data fetching and caching
  • React Native - Build mobile apps
  • Advanced Patterns - Compound components, state machines

Keep Building

The best way to learn is by building. Create your own projects:

  • Clone popular apps (Twitter, Instagram, Notion)
  • Build tools that solve your own problems
  • Contribute to open source projects
  • Share your projects and get feedback
  • Help others learn React

Thank you for following this tutorial series!

You've put in the work, built the knowledge, and created a complete application. You should be proud of what you've accomplished.

Keep coding, keep learning, and most importantly - keep building amazing things with React!

Happy coding! šŸš€

Just completed the entire React tutorial series and built a complete task manager app! šŸŽ‰

Previous
Testing React Components

Continue Your Learning Journey

You've completed the React series! Subscribe to get notified about new tutorials, advanced topics, project ideas, and exclusive content - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

React Tutorials

0 of 42 completed

Your Progress0%

Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

Ā© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo