Hone logo
Hone
Problems

Implement useMap Hook for Managing Key-Value Pairs in React

This challenge involves creating a custom React hook, useMap, that provides a robust and convenient way to manage key-value pairs within your React components. This hook will abstract away the complexities of state management for collections of data identified by unique keys, making your code cleaner and more readable.

Problem Description

You are tasked with building a custom React hook called useMap that behaves similarly to JavaScript's built-in Map object but is integrated with React's state management system. This hook should allow you to store, retrieve, update, and delete key-value pairs. The hook should return an object containing the current map's entries and functions to manipulate it.

Key Requirements:

  • Initialization: The hook should accept an optional initial Map or an iterable of key-value pairs (e.g., an array of [key, value] tuples) to pre-populate the map.
  • State Management: All operations on the map should trigger React re-renders when the map's state changes.
  • Core Operations: The hook must provide the following functions:
    • set(key: K, value: V): Adds or updates a key-value pair.
    • get(key: K): V | undefined: Retrieves the value associated with a key.
    • has(key: K): boolean: Checks if a key exists in the map.
    • delete(key: K): boolean: Removes a key-value pair and returns true if successful, false otherwise.
    • clear(): void: Removes all key-value pairs.
  • Accessing Data: The hook should return the current state of the map. This could be the Map object itself, or an array of its entries. Returning an array of entries ([key, value][]) is often more convenient for rendering in React.
  • Type Safety: The hook should be generic, accepting types for keys (K) and values (V).

Expected Behavior:

When a key is added, updated, deleted, or the map is cleared, the component using useMap should re-render to reflect the changes.

Edge Cases to Consider:

  • Handling non-primitive keys (objects, arrays).
  • Ensuring efficient updates to avoid unnecessary re-renders if the same key/value pair is set multiple times.

Examples

Example 1: Basic Usage

import React, { useState } from 'react';
import { useMap } from './useMap'; // Assuming your hook is in useMap.ts

const MyMapComponent: React.FC = () => {
  const { mapEntries, set, get, has, delete: deleteEntry, clear } = useMap<string, number>();

  const handleAdd = () => {
    const newKey = `item_${Date.now()}`;
    set(newKey, Math.floor(Math.random() * 100));
  };

  const handleUpdate = () => {
    if (mapEntries.length > 0) {
      const [keyToUpdate] = mapEntries[0];
      set(keyToUpdate, 999); // Update the first item
    }
  };

  const handleGetValue = (key: string) => {
    alert(`Value for ${key}: ${get(key)}`);
  };

  return (
    <div>
      <h2>Map Entries:</h2>
      <ul>
        {mapEntries.map(([key, value]) => (
          <li key={key}>
            {key}: {value}
            <button onClick={() => handleGetValue(key)}>Get Value</button>
            <button onClick={() => deleteEntry(key)}>Delete</button>
          </li>
        ))}
      </ul>
      <button onClick={handleAdd}>Add Random Item</button>
      <button onClick={handleUpdate}>Update First Item</button>
      <button onClick={clear}>Clear Map</button>
    </div>
  );
};

Input to the useMap hook: None (starts empty).

Output of the useMap hook (initial): { mapEntries: [], set: Function, get: Function, has: Function, delete: Function, clear: Function }

Explanation: The component initializes an empty map. Buttons allow adding new items, updating an existing item, getting values, deleting items, and clearing the entire map. The UI dynamically displays the map entries.

Example 2: Initializing with Data

import React from 'react';
import { useMap } from './useMap';

const InitializedMapComponent: React.FC = () => {
  const initialData: [string, string][] = [
    ['apple', 'red'],
    ['banana', 'yellow'],
  ];

  const { mapEntries, set, delete: deleteEntry } = useMap<string, string>(initialData);

  const handleAddOrange = () => {
    set('orange', 'orange');
  };

  return (
    <div>
      <h2>Fruits:</h2>
      <ul>
        {mapEntries.map(([key, value]) => (
          <li key={key}>
            {key}: {value}
            <button onClick={() => deleteEntry(key)}>Delete</button>
          </li>
        ))}
      </ul>
      <button onClick={handleAddOrange}>Add Orange</button>
    </div>
  );
};

Input to the useMap hook: [['apple', 'red'], ['banana', 'yellow']]

Output of the useMap hook (initial): { mapEntries: [['apple', 'red'], ['banana', 'yellow']], set: Function, delete: Function, ... }

Explanation: The useMap hook is initialized with an array of fruit-color pairs. The component displays these fruits and allows adding more.

Example 3: Handling Different Key Types

import React from 'react';
import { useMap } from './useMap';

interface User {
  id: number;
  name: string;
}

const ObjectKeyMapComponent: React.FC = () => {
  const user1: User = { id: 1, name: 'Alice' };
  const user2: User = { id: 2, name: 'Bob' };

  const { mapEntries, set, get } = useMap<User, string>();

  const handleAddUsers = () => {
    set(user1, 'Admin');
    set(user2, 'User');
  };

  const handleGetUserRole = (user: User) => {
    alert(`${user.name}'s role: ${get(user)}`);
  };

  return (
    <div>
      <h2>User Roles:</h2>
      <button onClick={handleAddUsers}>Add Users</button>
      <ul>
        {mapEntries.map(([user, role]) => (
          <li key={user.id}>
            {user.name}: {role}
            <button onClick={() => handleGetUserRole(user)}>Get Role</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

Input to the useMap hook: None (starts empty).

Output of the useMap hook (initial): { mapEntries: [], set: Function, get: Function, ... }

Explanation: This example demonstrates using complex objects as keys. The hook correctly distinguishes between different object instances if they are distinct, and associates values with them.

Constraints

  • The hook must be implemented in TypeScript.
  • The underlying state management should use React's useState or useReducer hook.
  • For efficiency, avoid creating new Map objects on every render if the state hasn't changed.
  • The mapEntries returned should be an array of [K, V] tuples.

Notes

  • Consider how you will handle the conversion from the internal Map to the returned mapEntries array.
  • Think about the implications of using mutable objects as keys. JavaScript's Map uses reference equality for object keys.
  • The delete operation should return a boolean indicating whether an element was successfully removed.
  • The get operation should return undefined if the key is not found.
  • Consider what happens if the initial iterable contains duplicate keys. The behavior should align with the standard Map constructor.
Loading editor...
typescript