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

> Learn to implement bonus features for intermediate App Ideas projects. Extend data models, add UI components, and persist state using localStorage for enhanced functionality.

- Repository: [Florin Pop/app-ideas](https://github.com/florinpop17/app-ideas)
- Tags: how-to-guide
- Published: 2026-02-27

---

**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`](https://github.com/florinpop17/app-ideas/blob/main/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:

```javascript
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:

```javascript
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:

```javascript
{
  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:

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

```

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

```javascript
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

```html
<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

```jsx
// 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`](https://github.com/florinpop17/app-ideas/blob/main/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.