An advanced Task Manager built with React (Vite, JavaScript) — filtering, theming, drag-and-drop reordering, and localStorage persistence. Fully self-contained in the repo root.
Live demo: https://limetrey-assignment.vercel.app/
| # | Requirement | Status |
|---|---|---|
| 1a | Add tasks | ✅ empty/whitespace-only text is rejected |
| 1b | Mark tasks as completed | ✅ strikethrough style |
| 1c | Delete tasks | ✅ |
| 1d | Filter tasks (All / Completed / Pending) | ✅ FilterBar |
| 1e | Persist tasks in Local Storage | ✅ useLocalStorage hook |
| 2a | Custom hooks | ✅ src/hooks/useLocalStorage.js |
| 2b | Context API instead of prop drilling | ✅ TaskContext + ThemeContext |
| 2c | Performance: React.memo, useCallback, useMemo |
✅ see Optimizations |
| 2d | Form validation (no empty tasks) | ✅ |
| 3a | Theming (Dark / Light toggle) | ✅ data-theme on <body>, CSS variables |
| 3b | Animations (add/remove task transitions) | ✅ pure CSS: fade + slide-in on add, fade-out before delete (220ms) |
| 3c | Responsive design (mobile-first) | ✅ single column → centered card ≥600px, 44px tap targets |
| 3d | Task drag-and-drop | ✅ @hello-pangea/dnd |
| — | Submission: commit to GitHub | ✅ see Deployment |
- Vite + React (JavaScript) — no TypeScript
- React Context API — task state, actions and theme state, no prop drilling
- Custom hook —
useLocalStorage(lazy JSON parse read,useEffectwrite, fallback on error) - @hello-pangea/dnd — the maintained fork of react-beautiful-dnd, which is unmaintained and breaks under React 18+ Strict Mode. Rows are draggable; checkbox and Delete stay interactive (library blocks drag from those controls).
- Plain CSS — mobile-first, CSS custom properties (
--bg,--text,--card-bg,--border,--accent, ...) for light/dark theming; CSS keyframes + transitions for all animations (no animation library) - Oxlint (from the Vite template) for linting
npm install
npm run devthen open the printed URL (http://localhost:5173). Node 18+ required.
useLocalStorage— localStorage read once via lazy initializer; writes viauseEffectonly, when value changesTaskItemwrapped inReact.memo— a row only re-renders when its own data changesaddTask,toggleTask,deleteTask,moveTask,toggleThemewrapped inuseCallback(empty deps via the updater form ofsetTasks) — stable references for memoization to workfilteredTasksinuseMemo— recomputed only whentasksorfilterchange- Provider
valueobjects wrapped inuseMemo— stable context identity prevents needless consumer re-renders moveTaskreorders within the visible/filtered order (hidden tasks keep their position) and persists throughuseLocalStorage- Filtering logic shared via a single
matchesFilterhelper — no duplication - Animations are pure CSS (
animationon mount,.exitingtransition before removal) — no transform/drag conflicts, so drag-and-drop stays buttery while tasks fade in/out
.
├── README.md
├── .gitignore
├── index.html
├── package.json
├── vite.config.js
├── screenshots/
│ ├── light.png
│ ├── dark.png
│ └── lighthouse.png
└── src/
├── main.jsx React entry
├── App.jsx composition root (providers + layout)
├── App.css mobile-first styles + theme CSS variables
├── index.css base reset
├── components/
│ ├── TaskForm.jsx add-task input + validation
│ ├── TaskItem.jsx single task row (memoized, draggable, animated)
│ ├── TaskList.jsx list + DragDropContext + Droppable
│ ├── FilterBar.jsx All / Completed / Pending
│ └── ThemeToggle.jsx light/dark switch
├── context/
│ ├── TaskContext.jsx tasks, actions, filter, filteredTasks
│ └── ThemeContext.jsx theme state, syncs `data-theme` to <body>
└── hooks/
└── useLocalStorage.js persistent useState
Google Lighthouse audit of the live demo — 100 in Performance, Accessibility, Best Practices, and SEO:


