Hone logo
Hone
Problems

React Event Emitter Implementation

Building a custom event emitter within a React component can be useful for decoupling components and facilitating communication between them without direct parent-child relationships. This challenge asks you to implement a basic event emitter class that can be integrated into a React component, allowing it to emit and subscribe to custom events. This is a common pattern for creating reusable component logic and managing state changes across a component tree.

Problem Description

You are tasked with creating a TypeScript class named EventEmitter that provides the core functionality of an event emitter. This class should allow components to:

  1. Subscribe to events: A component should be able to register a callback function to be executed when a specific event is emitted. The subscription should accept an event name (string) and a callback function.
  2. Emit events: A component should be able to trigger an event, notifying all subscribed callbacks for that event. The emission should accept an event name (string) and optional data to be passed to the callbacks.
  3. Unsubscribe from events: A component should be able to remove a previously registered callback from a specific event. The unsubscription should accept the event name (string) and the callback function that was originally subscribed.

The EventEmitter class should manage a private data structure (e.g., a Map or object) to store the event listeners. Ensure that the class handles cases where an event has no listeners gracefully.

Key Requirements:

  • The class must be written in TypeScript.
  • The class must provide subscribe, emit, and unsubscribe methods.
  • The subscribe method should return a function that, when called, will unsubscribe the callback. This allows for easy unsubscription.
  • The emit method should pass the provided data to the callback function.
  • The class should handle cases where an event is emitted but no listeners are present.
  • The class should handle attempts to unsubscribe a callback that was not previously subscribed.

Expected Behavior:

  • Subscribing to an event should store the callback function associated with that event.
  • Emitting an event should execute all registered callbacks for that event, passing the provided data to each callback.
  • Unsubscribing from an event should remove the specified callback function from the list of listeners for that event.
  • Calling the unsubscribe function returned by subscribe should remove the callback.

Edge Cases to Consider:

  • Event names are strings.
  • Callbacks are functions.
  • Data passed to emit can be of any type.
  • Multiple callbacks can be subscribed to the same event.
  • Attempting to unsubscribe a callback that was never subscribed.
  • Emitting an event with no listeners.

Examples

Example 1:

Input:
emitter = new EventEmitter();
const callback = (data: string) => console.log("Received:", data);
const unsubscribe = emitter.subscribe("myEvent", callback);
emitter.emit("myEvent", "Hello, world!");
emitter.unsubscribe("myEvent", callback);
emitter.emit("myEvent", "Another message");

Output:
"Received: Hello, world!" (printed to console)
// No output for the second emit because the callback has been unsubscribed.

Explanation: The callback is initially subscribed to "myEvent". When "myEvent" is emitted with "Hello, world!", the callback is executed. Then, the callback is unsubscribed, so subsequent emissions of "myEvent" are ignored.

Example 2:

Input:
emitter = new EventEmitter();
const callback1 = (data: number) => console.log("Callback 1:", data);
const callback2 = (data: number) => console.log("Callback 2:", data);

const unsubscribe1 = emitter.subscribe("numberEvent", callback1);
const unsubscribe2 = emitter.subscribe("numberEvent", callback2);

emitter.emit("numberEvent", 123);
unsubscribe1(); // Unsubscribe callback1
emitter.emit("numberEvent", 456);

Output:
"Callback 1: 123"
"Callback 2: 123"
"Callback 2: 456"

Explanation: Two callbacks are subscribed to "numberEvent". Both are executed when the event is first emitted. unsubscribe1() removes callback1, so it is not executed on subsequent emissions.

Example 3: (Edge Case)

Input:
emitter = new EventEmitter();
emitter.emit("nonExistentEvent", "Some data");

Output:
// No output.  The emit call is silently ignored because there are no listeners.

Explanation: Emitting an event with no subscribers should not cause an error; it should simply be ignored.

Constraints

  • The EventEmitter class should be lightweight and efficient.
  • Event names must be strings.
  • Callbacks must be functions.
  • Data passed to emit can be of any type.
  • The class should not rely on external libraries.
  • The class should be designed to be easily integrated into React components.

Notes

  • Consider using a Map or a plain JavaScript object to store event listeners. A Map offers better performance for frequent additions and removals.
  • Think about how to handle the case where a callback is unsubscribed multiple times.
  • The returned unsubscribe function should be a closure that captures the event name and callback function.
  • Focus on creating a clean and well-documented class.
  • This is a foundational building block for more complex event handling systems.
Loading editor...
typescript