How to Implement Bonus Features for Intermediate-Level Projects in the App-Ideas Repository

To implement bonus features for intermediate-level projects in the App-Ideas repository, extend the base data model with additional fields (e.g., timestamps, completion status), add UI components for editing and filtering, and persist state using localStorage.

The florinpop17/app-ideas repository provides structured project specifications for developers of all skill levels. Intermediate projects include optional bonus features that transform basic implementations into production-ready applications. This guide demonstrates how to implement these bonus features using the To-Do App as a concrete example, with architectural patterns applicable to other intermediate projects like the Voting App or Quiz App.

Understanding the Baseline Requirements

Before implementing bonus features, ensure your application satisfies the core user stories defined in the specification file. For the To-Do App located at Projects/2-Intermediate/To-Do-App.md, baseline requirements include:

  • Input field for a new task
  • Adding tasks to a list on Enter or button click
  • Marking tasks as completed
  • Removing tasks

These core features establish the foundation upon which bonus functionality is built.

Bonus Feature Checklist for Intermediate Projects

The specification file defines five distinct bonus features in the Bonus features section. Each requires specific architectural changes:

Feature Implementation Requirement
Edit a to-do In-place editing with input field replacement
List of completed to-dos Filtered view of completed: true items
List of active to-dos Filtered view of completed: false items
Creation date Timestamp storage and formatting
Persist data across sessions localStorage integration

Edit Functionality

Implementing edit mode requires tracking an "editing" state for each task. When a user triggers an edit action (via button click or double-click), replace the text display with an <input> element pre-populated with the current value. On blur or Enter keypress, update the task object in your data array.

Filtering and Lists

Derive filtered arrays using pure functions rather than duplicating data. Create activeTasks and completedTasks by filtering the master array:

const activeTasks = tasks.filter(t => !t.completed);
const completedTasks = tasks.filter(t => t.completed);

Render these in separate sections or tabbed interfaces to satisfy the filtering bonus features.

Data Persistence

The specification references MDN documentation for localStorage in the Useful links section. Implement two helper functions to serialize and deserialize your task array:

function saveTasks(tasks) {
  localStorage.setItem('tasks', JSON.stringify(tasks));
}

function loadTasks() {
  const data = localStorage.getItem('tasks');
  return data ? JSON.parse(data) : [];
}

Architectural Implementation

Extending the Data Model

The baseline implementation typically stores simple strings. To support bonus features, migrate to an object-based schema:

{
  id: 'uuid',          // unique identifier for key prop and lookups
  text: 'Buy milk',    // user-entered description
  completed: false,    // boolean flag for status
  createdAt: Date.now() // timestamp for creation date display (bonus #4)
}

Use libraries like uuid or native crypto.randomUUID() to generate unique identifiers.

UI Layer Modifications

Structure your components to handle three distinct modes: display, edit, and filter.

  • Display mode: Show task text, completion checkbox, and action buttons (edit/delete)
  • Edit mode: Replace text with controlled input bound to temporary state
  • Filter controls: Buttons or tabs that set a visibility filter (all, active, completed)

localStorage Integration

Initialize your application state by loading persisted data:

// React example
useEffect(() => {
  const persisted = loadTasks();
  setTasks(persisted);
}, []);

Synchronize changes using a side effect that triggers whenever the tasks array mutates:

useEffect(() => {
  saveTasks(tasks);
}, [tasks]);

For vanilla JavaScript implementations, call saveTasks() immediately after every state-modifying operation (add, toggle, edit, delete).

Code Examples

Vanilla JavaScript Implementation

<ul id="list"></ul>

<script>
let tasks = loadTasks() || [];

function render() {
  const ul = document.getElementById('list');
  ul.innerHTML = '';
  
  tasks.forEach(task => {
    const li = document.createElement('li');
    li.textContent = `${task.text} (${new Date(task.createdAt).toLocaleDateString()})`;
    li.dataset.id = task.id;
    
    // Edit button
    const editBtn = document.createElement('button');
    editBtn.textContent = '✏️';
    editBtn.onclick = () => startEdit(task.id);
    li.appendChild(editBtn);
    
    ul.appendChild(li);
  });
}

function startEdit(id) {
  const task = tasks.find(t => t.id === id);
  const newText = prompt('Edit task', task.text);
  if (newText !== null) {
    task.text = newText;
    saveTasks(tasks);
    render();
  }
}

// Initialize
render();
</script>

React Implementation

// src/App.jsx
import { useState, useEffect } from 'react';
import { v4 as uuidv4 } from 'uuid';

function App() {
  const [tasks, setTasks] = useState([]);
  const [filter, setFilter] = useState('all'); // 'all' | 'active' | 'completed'

  // Load persisted tasks on mount
  useEffect(() => {
    const persisted = loadTasks();
    setTasks(persisted);
  }, []);

  // Persist whenever tasks change
  useEffect(() => {
    saveTasks(tasks);
  }, [tasks]);

  const addTask = (text) => {
    setTasks(prev => [...prev, { 
      id: uuidv4(), 
      text, 
      completed: false, 
      createdAt: Date.now() 
    }]);
  };

  const toggleComplete = (id) => {
    setTasks(prev => prev.map(t => 
      t.id === id ? { ...t, completed: !t.completed } : t
    ));
  };

  const editTask = (id, newText) => {
    setTasks(prev => prev.map(t => 
      t.id === id ? { ...t, text: newText } : t
    ));
  };

  const deleteTask = (id) => {
    setTasks(prev => prev.filter(t => t.id !== id));
  };

  // Filtering for bonus features
  const filteredTasks = tasks.filter(t => {
    if (filter === 'active') return !t.completed;
    if (filter === 'completed') return t.completed;
    return true;
  });

  return (
    <div className="app">
      <h1>To-Do App</h1>
      <TaskInput onAdd={addTask} />
      <FilterTabs current={filter} onChange={setFilter} />
      <TaskList 
        tasks={filteredTasks} 
        onToggle={toggleComplete}
        onDelete={deleteTask}
        onEdit={editTask}
      />
    </div>
  );
}

Generalizing to Other Intermediate Projects

The architectural patterns demonstrated in the To-Do App apply consistently across other intermediate projects in the repository, such as the Voting App or Quiz App.

Extend the data schema to accommodate bonus requirements. For a Voting App, add createdAt timestamps and voterId tracking. For a Quiz App, add timeSpent per question and category tags.

Create pure filter functions rather than maintaining separate state arrays. Whether filtering polls by date range or questions by difficulty, derive views from a single source of truth.

Implement persistence early. The localStorage pattern shown above works for any JSON-serializable data structure. For larger datasets (e.g., image-heavy projects), consider IndexedDB.

Summary

  • Extend the data model to include id, createdAt, and status flags required for bonus features.
  • Implement editing by toggling between display and input modes, updating the master array on confirmation.
  • Filter views using Array.filter to create active and completed lists without duplicating state.
  • Persist data using localStorage helpers that serialize on every mutation and deserialize on app initialization.
  • Apply patterns universally across intermediate projects by extending schemas and maintaining pure transformation functions.

Frequently Asked Questions

What are the most common bonus features in App-Ideas intermediate projects?

Most intermediate projects in the florinpop17/app-ideas repository include bonus features such as in-place editing, filtering by status (active vs. completed), timestamp tracking (creation dates), and local data persistence using localStorage. These features are documented in each project's specification file under the Bonus features section, such as in Projects/2-Intermediate/To-Do-App.md.

How do I handle data persistence without a backend?

Implement client-side storage using the localStorage API, which is referenced in the Useful links section of intermediate project specifications. Create helper functions to JSON.stringify your data before saving and JSON.parse when loading. Initialize your application state by calling the load function on mount, and persist changes by calling the save function after every create, update, or delete operation.

Can I use IndexedDB instead of localStorage for bonus features?

Yes, IndexedDB is suitable for intermediate projects that handle larger datasets or require complex querying beyond simple key-value storage. While the App-Ideas specifications reference localStorage in the Useful links section, IndexedDB provides better performance for image-heavy applications or projects requiring offline functionality. Implement IndexedDB using wrapper libraries like idb or localForage to maintain a similar API surface to localStorage.

How do I structure my code to support editing and filtering?

Adopt a single source of truth architecture. Store all tasks in one master array and derive filtered views (active, completed) using pure functions like Array.filter. For editing, maintain a temporary editing state (e.g., editingId) that determines whether to render a text display or an input field. Update the master array immutably when edits are confirmed, ensuring that both filtered views and persistence layers automatically reflect the changes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →