Custom React Hook: useList
Develop a custom React hook, useList, that simplifies common list manipulation operations. This hook will provide a stateful list and functions to add, remove, update, and clear elements, making it easier to manage dynamic lists within React components.
Problem Description
You are tasked with creating a custom React hook named useList. This hook should manage an array of any type and expose methods to interact with that array. The goal is to abstract away the common patterns of list management in React, such as adding new items, removing existing ones, updating specific items, and clearing the entire list, into a reusable and declarative hook.
Key Requirements:
- State Management: The hook must maintain a stateful list (an array).
- Initial State: The hook should accept an optional initial list value. If not provided, it should default to an empty array.
- Core Operations: The hook must expose the following functions:
add(item): Adds a newitemto the end of the list.remove(index): Removes the item at the givenindexfrom the list.update(index, newItem): Replaces the item at the givenindexwithnewItem.clear(): Removes all items from the list.set(newList): Replaces the entire current list withnewList.
- Return Value: The hook should return an object containing the current list state and all the operation functions.
- Type Safety: The hook should be implemented in TypeScript and be generic to work with lists of any data type.
Expected Behavior:
When the hook is used in a React component, the component should re-render whenever the list state changes due to any of the provided operations. The operations should modify the internal state immutably.
Edge Cases:
- Handling invalid indices for
removeandupdateoperations (e.g., negative indices, indices out of bounds). The hook should ideally not throw errors but rather do nothing or handle them gracefully (e.g., no change to the list). - Adding
undefinedornullto the list.
Examples
Example 1:
import { useList } from './useList'; // Assuming your hook is in useList.ts
function MyComponent() {
const { list, add, remove, update, clear } = useList<string>(['apple', 'banana']);
return (
<div>
<ul>
{list.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => remove(index)}>Remove</button>
<button onClick={() => update(index, item.toUpperCase())}>Uppercase</button>
</li>
))}
</ul>
<button onClick={() => add('orange')}>Add Orange</button>
<button onClick={clear}>Clear List</button>
</div>
);
}
Output (after initial render):
<div>
<ul>
<li>apple<button>Remove</button><button>Uppercase</button></li>
<li>banana<button>Remove</button><button>Uppercase</button></li>
</ul>
<button>Add Orange</button>
<button>Clear List</button>
</div>
Explanation:
The useList hook is initialized with ['apple', 'banana']. The component renders the initial list and buttons to manipulate it.
Example 2:
User clicks "Add Orange"
State Change: list becomes ['apple', 'banana', 'orange']
Example 3:
User clicks "Remove" for "apple" (index 0)
State Change: list becomes ['banana', 'orange']
Example 4:
User clicks "Uppercase" for "banana" (index 0)
State Change: list becomes ['BANANA', 'orange']
Example 5:
User clicks "Clear List"
State Change: list becomes []
Example 6: (Edge Case - Invalid Index)
// ... inside MyComponent
// Assume list is ['apple', 'banana']
remove(5); // Calling remove with an index out of bounds
State Change: No change to the list. The hook gracefully handles the out-of-bounds index.
Constraints
- The hook must be implemented in TypeScript.
- The hook should accept a generic type
<T>for the list elements. - The initial list provided to the hook can be
undefined. - All list modifications must be performed immutably.
- Performance: The hook should be efficient for typical list sizes (up to a few hundred items). For extremely large lists, further optimizations might be considered, but standard
useStateand immutable updates are expected.
Notes
- Consider using
React.useStateinternally to manage the list state. - When implementing
removeandupdate, think about how to create a new array without mutating the original. Array methods likeslice,filter,map, and the spread syntax (...) can be useful. - Ensure that the functions returned by the hook are stable references (e.g., using
useCallbackif necessary, though for simple functions like these, it might not be strictly required unless they are passed as dependencies to other hooks or components). - Pay attention to how you handle array indices to prevent unexpected behavior.