Todo Application with Advanced Features

Master machine coding by creating a feature-rich Todo application with filtering, persistence, real-time updates, and modern React patterns.

Machine Coding Challenge: Advanced Todo Application

Create a production-ready Todo application that demonstrates advanced React patterns, state management, and real-world UX considerations.


Problem Statement

Design and implement a Todo application with the following requirements:

✅ Core Features (Must Have)

  • Add, edit, and delete todos
  • Mark todos as complete/incomplete
  • Filter todos by status (All, Active, Completed)
  • Persist todos in localStorage
  • Responsive design for mobile and desktop

Advanced Features (Nice to Have)

  • Real-time search with debouncing
  • Bulk actions (select all, delete selected)
  • Drag and drop reordering
  • Due date functionality with reminders
  • Dark/Light theme toggle
  • Keyboard shortcuts support

UI/UX Requirements

  • Clean, modern interface
  • Smooth animations and transitions
  • Loading states and error handling
  • Empty state design
  • Accessibility compliance (ARIA labels, keyboard navigation)

Clarifying Questions & Scope

Before diving into implementation, clarify these aspects:

Functional Requirements

  • Todo Structure: What fields should each todo have? (title, description, due date, priority, tags?)
  • Filtering: Should we support multiple filters simultaneously?
  • Persistence: localStorage only, or should we simulate API calls?
  • Real-time: Should changes sync across multiple browser tabs?

Technical Constraints

  • Time Limit: 2-3 hours for implementation
  • Framework: React with hooks (no external state management initially)
  • Styling: CSS-in-JS, Tailwind, or plain CSS?
  • Browser Support: Modern browsers only or IE11+?

Success Criteria

  • Clean, maintainable code structure
  • Proper error handling and edge cases
  • Responsive and accessible design
  • Performance optimization for large lists

High-Level Architecture

1. Component Structure

TodoApp/
├── TodoHeader/          # Search, filters, bulk actions
├── TodoList/           # Main todo container
│   ├── TodoItem/       # Individual todo component
│   └── TodoEmpty/      # Empty state component
├── TodoForm/           # Add/edit todo form
├── TodoFilters/        # Filter controls
└── TodoStats/          # Progress indicators

2. State Management Strategy

interface Todo {
  id: string;
  title: string;
  description?: string;
  completed: boolean;
  createdAt: Date;
  dueDate?: Date;
  priority: 'low' | 'medium' | 'high';
  tags: string[];
}

interface AppState {
  todos: Todo[];
  filter: 'all' | 'active' | 'completed';
  searchQuery: string;
  selectedTodos: Set<string>;
  theme: 'light' | 'dark';
  isLoading: boolean;
}

3. Data Flow

User Action → Event Handler → State Update → Re-render → UI Update

localStorage Sync ← Persistence Layer ← State Change

🛠️ Step-by-Step Implementation

Step 1: Project Setup & Basic Structure

// App.tsx
import React, { useState, useEffect } from 'react';
import TodoHeader from './components/TodoHeader';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';
import { useLocalStorage } from './hooks/useLocalStorage';
import { Todo, FilterType } from './types';

const App: React.FC = () => {
  const [todos, setTodos] = useLocalStorage<Todo[]>('todos', []);
  const [filter, setFilter] = useState<FilterType>('all');
  const [searchQuery, setSearchQuery] = useState('');
  const [selectedTodos, setSelectedTodos] = useState<Set<string>>(new Set());

  // Filtered and searched todos
  const filteredTodos = todos
    .filter(todo => {
      if (filter === 'active') return !todo.completed;
      if (filter === 'completed') return todo.completed;
      return true;
    })
    .filter(todo => 
      todo.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
      todo.description?.toLowerCase().includes(searchQuery.toLowerCase())
    );

  return (
    <div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
      <div className="max-w-4xl mx-auto p-6">
        <TodoHeader 
          searchQuery={searchQuery}
          onSearchChange={setSearchQuery}
          filter={filter}
          onFilterChange={setFilter}
          selectedCount={selectedTodos.size}
          onBulkDelete={() => {/* implementation */}}
        />
        
        <TodoForm onAddTodo={addTodo} />
        
        <TodoList 
          todos={filteredTodos}
          selectedTodos={selectedTodos}
          onTodoToggle={toggleTodo}
          onTodoDelete={deleteTodo}
          onTodoEdit={editTodo}
          onTodoSelect={toggleTodoSelection}
        />
      </div>
    </div>
  );
};

Step 2: Custom Hooks for Reusability

// hooks/useLocalStorage.ts
import { useState, useEffect } from 'react';

export function useLocalStorage<T>(
  key: string, 
  initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  });

  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(`Error setting localStorage key "${key}":`, error);
    }
  };

  return [storedValue, setValue];
}

Step 3: Core Components Implementation

// components/TodoForm.tsx
import React, { useState } from 'react';

interface TodoFormProps {
  onAddTodo: (todo: Omit<Todo, 'id' | 'createdAt'>) => void;
}

const TodoForm: React.FC<TodoFormProps> = ({ onAddTodo }) => {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!title.trim()) return;

    onAddTodo({
      title: title.trim(),
      description: description.trim(),
      completed: false,
      priority: 'medium',
      tags: [],
    });

    setTitle('');
    setDescription('');
  };

  return (
    <form onSubmit={handleSubmit} className="mb-6">
      <div className="flex gap-4">
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="What needs to be done?"
          className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
          required
        />
        <button
          type="submit"
          className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
        >
          Add Todo
        </button>
      </div>
    </form>
  );
};

Step 4: Advanced Features Implementation

// hooks/useDebounce.ts
import { useState, useEffect } from 'react';

export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// components/TodoHeader.tsx
import React from 'react';
import { useDebounce } from '../hooks/useDebounce';

interface TodoHeaderProps {
  searchQuery: string;
  onSearchChange: (query: string) => void;
  filter: FilterType;
  onFilterChange: (filter: FilterType) => void;
  selectedCount: number;
  onBulkDelete: () => void;
}

const TodoHeader: React.FC<TodoHeaderProps> = ({
  searchQuery,
  onSearchChange,
  filter,
  onFilterChange,
  selectedCount,
  onBulkDelete,
}) => {
  const debouncedSearchQuery = useDebounce(searchQuery, 300);

  return (
    <div className="mb-6 space-y-4">
      {/* Search Input */}
      <div className="relative">
        <input
          type="text"
          value={searchQuery}
          onChange={(e) => onSearchChange(e.target.value)}
          placeholder="Search todos..."
          className="w-full px-4 py-2 pl-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
        />
        <SearchIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
      </div>

      {/* Filters and Bulk Actions */}
      <div className="flex items-center justify-between">
        <div className="flex gap-2">
          {(['all', 'active', 'completed'] as const).map((filterType) => (
            <button
              key={filterType}
              onClick={() => onFilterChange(filterType)}
              className={`px-4 py-2 rounded-lg transition-colors ${
                filter === filterType
                  ? 'bg-blue-600 text-white'
                  : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
              }`}
            >
              {filterType.charAt(0).toUpperCase() + filterType.slice(1)}
            </button>
          ))}
        </div>

        {selectedCount > 0 && (
          <button
            onClick={onBulkDelete}
            className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
          >
            Delete Selected ({selectedCount})
          </button>
        )}
      </div>
    </div>
  );
};

UI/UX Enhancements

Responsive Design

/* Tailwind classes for responsive design */
.todo-container {
  @apply max-w-4xl mx-auto p-4 md:p-6;
}

.todo-item {
  @apply flex items-center gap-4 p-4 bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow;
}

@media (max-width: 768px) {
  .todo-item {
    @apply flex-col items-start gap-2;
  }
}

Accessibility Features

// Enhanced TodoItem with accessibility
const TodoItem: React.FC<TodoItemProps> = ({ todo, onToggle, onDelete, onEdit }) => {
  return (
    <div
      role="listitem"
      className="todo-item"
      aria-label={`Todo: ${todo.title}`}
    >
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
        aria-label={`Mark "${todo.title}" as ${todo.completed ? 'incomplete' : 'complete'}`}
        className="w-5 h-5 text-blue-600 rounded focus:ring-blue-500"
      />
      
      <span
        className={`flex-1 ${todo.completed ? 'line-through text-gray-500' : ''}`}
      >
        {todo.title}
      </span>
      
      <div className="flex gap-2">
        <button
          onClick={() => onEdit(todo)}
          aria-label={`Edit "${todo.title}"`}
          className="p-2 text-gray-600 hover:text-blue-600 transition-colors"
        >
          <EditIcon className="w-4 h-4" />
        </button>
        
        <button
          onClick={() => onDelete(todo.id)}
          aria-label={`Delete "${todo.title}"`}
          className="p-2 text-gray-600 hover:text-red-600 transition-colors"
        >
          <DeleteIcon className="w-4 h-4" />
        </button>
      </div>
    </div>
  );
};

Performance Optimizations

Memoization for Large Lists

// Optimized TodoList with React.memo
const TodoItem = React.memo<TodoItemProps>(({ todo, onToggle, onDelete, onEdit }) => {
  // Component implementation
});

// Virtual scrolling for very large lists
import { FixedSizeList as List } from 'react-window';

const VirtualizedTodoList: React.FC<{ todos: Todo[] }> = ({ todos }) => {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <TodoItem todo={todos[index]} />
    </div>
  );

  return (
    <List
      height={600}
      itemCount={todos.length}
      itemSize={80}
      width="100%"
    >
      {Row}
    </List>
  );
};

Efficient State Updates

// Optimized state updates
const addTodo = useCallback((newTodo: Omit<Todo, 'id' | 'createdAt'>) => {
  const todo: Todo = {
    ...newTodo,
    id: generateId(),
    createdAt: new Date(),
  };
  
  setTodos(prev => [...prev, todo]);
}, []);

const toggleTodo = useCallback((id: string) => {
  setTodos(prev => 
    prev.map(todo => 
      todo.id === id 
        ? { ...todo, completed: !todo.completed }
        : todo
    )
  );
}, []);

Key Takeaways

What You've Learned

  1. Component Architecture: Breaking complex UIs into logical, reusable components
  2. State Management: Efficient data flow with React hooks and custom hooks
  3. Performance Optimization: Memoization, virtualization, and efficient updates
  4. User Experience: Responsive design, accessibility, and smooth interactions
  5. Code Quality: Clean, maintainable, and testable code structure

Common Pitfalls to Avoid

  • Over-engineering: Don't add complexity unless required
  • Poor State Management: Avoid prop drilling and inefficient updates
  • Ignoring Edge Cases: Always handle empty states and error conditions
  • Accessibility Neglect: Ensure keyboard navigation and screen reader support
  • Performance Issues: Optimize for large datasets and smooth interactions

Next Steps

  • Implement drag and drop functionality
  • Add real-time collaboration features
  • Integrate with a backend API
  • Add comprehensive test coverage
  • Deploy to production environment

This Todo application demonstrates the core principles of modern React development and serves as an excellent foundation for more complex applications. The patterns and techniques you've learned here will be invaluable in your machine coding interviews and real-world projects!