Hone logo
Hone
Problems

Immutable Updates in React with TypeScript

React relies on immutability for efficient state updates and to trigger re-renders correctly. This challenge focuses on practicing the creation of new state objects or arrays based on existing ones, without directly modifying the original data. Mastering this is crucial for building predictable and performant React applications.

Problem Description

You are tasked with building a simple To-Do list application component in React using TypeScript. The core functionality involves adding new items, marking items as completed, and removing items. All state updates must be performed immutably. This means you should never directly push to an array, modify a property of an object in place, or change any part of the existing state. Instead, you must always create a new array or object that reflects the desired changes.

Key Requirements:

  1. State Structure: The To-Do list state should be an array of objects. Each To-Do object should have at least id (number), text (string), and completed (boolean) properties.
  2. Add To-Do: Implement a function that adds a new To-Do item to the list. The new item should be appended to the end of the existing list.
  3. Toggle To-Do Completion: Implement a function that toggles the completed status of a specific To-Do item identified by its id.
  4. Remove To-Do: Implement a function that removes a To-Do item from the list based on its id.

Expected Behavior:

  • When a new To-Do is added, the original state array should remain unchanged, and a new array containing all previous To-Dos plus the new one should be returned.
  • When a To-Do's completion status is toggled, the original To-Do object within the state array should not be mutated. Instead, a new array with a new To-Do object for the toggled item (with the updated completed status) should be returned. Other To-Do objects should remain references to their original objects.
  • When a To-Do is removed, the original state array should remain unchanged, and a new array excluding the removed To-Do should be returned.

Examples

Example 1: Adding a To-Do

Initial State:
[
  { id: 1, text: "Learn React", completed: false },
  { id: 2, text: "Build a To-Do App", completed: false }
]

Action: Add "Drink water" (assume next ID is 3)

Output State:
[
  { id: 1, text: "Learn React", completed: false },
  { id: 2, text: "Build a To-Do App", completed: false },
  { id: 3, text: "Drink water", completed: false }
]

Explanation: A new array was created, containing the original two To-Dos and the newly added one. The original array was not modified.

Example 2: Toggling To-Do Completion

Initial State:
[
  { id: 1, text: "Learn React", completed: false },
  { id: 2, text: "Build a To-Do App", completed: false }
]

Action: Toggle completion for To-Do with id 1

Output State:
[
  { id: 1, text: "Learn React", completed: true },
  { id: 2, text: "Build a To-Do App", completed: false }
]

Explanation: A new array was created. The To-Do with id 1 was replaced by a new object with `completed: true`. The To-Do with id 2 remains the same object reference.

Example 3: Removing a To-Do

Initial State:
[
  { id: 1, text: "Learn React", completed: true },
  { id: 2, text: "Build a To-Do App", completed: false },
  { id: 3, text: "Drink water", completed: false }
]

Action: Remove To-Do with id 2

Output State:
[
  { id: 1, text: "Learn React", completed: true },
  { id: 3, text: "Drink water", completed: false }
]

Explanation: A new array was created, containing only the To-Dos that were not removed. The original array was not modified.

Constraints

  • The id for new To-Dos will be a positive integer, and you can assume a mechanism to generate unique IDs will be provided (e.g., a counter).
  • Input To-Do text will be a non-empty string.
  • The state update functions should be pure functions, meaning they produce the same output for the same input and have no side effects.
  • You should utilize standard JavaScript array and object manipulation methods that promote immutability (e.g., map, filter, slice, spread syntax ..., Object.assign). Avoid methods like push, pop, splice, sort (unless used immutably), or direct object property assignment (state.items[index].completed = ...).

Notes

Consider how you would structure these functions within a React component's useState hook or a useReducer hook. The goal is to write these immutable update functions in TypeScript, which will be the core logic for your state management. For the examples, you can think of these as standalone functions that take the current state and an action/payload and return the new state.

When toggling an item, ensure that you create a new object for the item being modified, rather than mutating the existing one. This is a common pitfall. For example, return { ...todo, completed: !todo.completed }; is the correct immutable approach for updating a single item.

Loading editor...
typescript