Hone logo
Hone
Problems

Building a Simple Reactive Data Store in JavaScript

This challenge asks you to implement a fundamental component of modern JavaScript applications: a reactive data store. A reactive system allows for automatic updates to the user interface or other parts of your application when the underlying data changes. This is crucial for creating dynamic and responsive user experiences.

Problem Description

You need to create a JavaScript class, ReactiveStore, that manages a piece of data and notifies any registered "subscribers" whenever that data is updated. The ReactiveStore should allow you to:

  1. Initialize with an initial value.
  2. Get the current value.
  3. Set a new value, triggering notifications.
  4. Subscribe to changes, providing a callback function that will be executed whenever the data is updated. Subscribers should also receive the new value.
  5. Unsubscribe from changes, removing a previously registered callback.

Key Requirements:

  • The ReactiveStore class should be generic enough to hold any type of data (primitives, objects, arrays, etc.).
  • When set is called with the same value as the current value, no subscribers should be notified.
  • When set is called with a new value, all currently subscribed callbacks should be invoked with the new value as an argument.
  • A subscriber can unsubscribe itself, or another subscriber can be removed by the store owner.
  • Subscribers should be able to unsubscribe multiple times without errors.

Expected Behavior:

  • Creating a store: const myStore = new ReactiveStore(initialValue);
  • Getting the value: myStore.get();
  • Setting a new value: myStore.set(newValue); (triggers subscribers)
  • Subscribing: const unsubscribe = myStore.subscribe(callbackFunction);
  • Unsubscribing: unsubscribe(); or myStore.unsubscribe(callbackFunction);

Edge Cases:

  • Setting the same value multiple times consecutively.
  • Subscribing with a function that immediately unsubscribes itself.
  • Unsubscribing a non-existent subscriber.
  • Handling initial values that are null or undefined.

Examples

Example 1: Basic Get, Set, and Subscribe

const countStore = new ReactiveStore(0);

let lastCount = -1;
const countSubscriber = (newValue) => {
    lastCount = newValue;
    console.log(`Count updated to: ${newValue}`);
};

const unsubscribeCount = countStore.subscribe(countSubscriber);

console.log("Initial count:", countStore.get()); // Output: Initial count: 0

countStore.set(10);
// Output: Count updated to: 10
console.log("Current count after set:", countStore.get()); // Output: Current count after set: 10

countStore.set(10); // Setting the same value
// No output from subscriber

countStore.set(25);
// Output: Count updated to: 25
console.log("Current count after another set:", countStore.get()); // Output: Current count after another set: 25

unsubscribeCount(); // Unsubscribe the listener
countStore.set(50);
// No output from subscriber
console.log("Current count after unsubscribe:", countStore.get()); // Output: Current count after unsubscribe: 50

Example 2: Object data and direct unsubscribe

const userStore = new ReactiveStore({ name: "Alice", age: 30 });

const userLogger = (newUser) => {
    console.log(`User changed: ${newUser.name}, Age: ${newUser.age}`);
};

userStore.subscribe(userLogger);

console.log("Initial user:", userStore.get()); // Output: Initial user: { name: "Alice", age: 30 }

userStore.set({ name: "Bob", age: 31 });
// Output: User changed: Bob, Age: 31
console.log("Current user:", userStore.get()); // Output: Current user: { name: "Bob", age: 31 }

// Let's say Bob's age changes
userStore.set({ name: "Bob", age: 32 });
// Output: User changed: Bob, Age: 32
console.log("Current user:", userStore.get()); // Output: Current user: { name: "Bob", age: 32 }

// Unsubscribe directly using the store method
userStore.unsubscribe(userLogger);
userStore.set({ name: "Charlie", age: 35 });
// No output from subscriber
console.log("Current user:", userStore.get()); // Output: Current user: { name: "Charlie", age: 35 }

Example 3: Multiple subscribers and self-unsubscribing

const themeStore = new ReactiveStore("light");

let themeLog1 = "";
const unsubscribe1 = themeStore.subscribe((newTheme) => {
    themeLog1 += `Listener 1 received: ${newTheme}\n`;
    if (newTheme === "dark") {
        console.log("Listener 1 unsubscribing itself!");
        unsubscribe1(); // Listener 1 unsubscribes itself
    }
});

let themeLog2 = "";
const listener2 = (newTheme) => {
    themeLog2 += `Listener 2 received: ${newTheme}\n`;
};
themeStore.subscribe(listener2);

themeStore.set("light");
// Listener 1 received: light
// Listener 2 received: light

themeStore.set("dark");
// Listener 1 received: dark
// Listener 2 received: dark
// Listener 1 unsubscribing itself!

themeStore.set("blue");
// Listener 2 received: blue
// Note: Listener 1 is no longer active.

console.log("--- Theme Logs ---");
console.log(themeLog1);
console.log(themeLog2);
/*
Expected output for logs:
--- Theme Logs ---
Listener 1 received: light
Listener 1 received: dark

Listener 2 received: light
Listener 2 received: dark
Listener 2 received: blue
*/

Constraints

  • The ReactiveStore class must be implemented purely in JavaScript. No external libraries or frameworks are allowed.
  • The implementation should be efficient; the number of subscribers could potentially be large, so operations like subscribe and unsubscribe should have a reasonable time complexity.
  • The set method should perform a shallow comparison when checking if the value has changed. For primitive types, this is a direct comparison. For objects and arrays, newValue === currentValue will suffice for this challenge.

Notes

  • Consider how you will store the list of subscribers. An array or a Set would be good candidates.
  • When handling subscriptions and unsubscriptions, ensure that the list of subscribers is managed correctly to avoid issues like trying to call an unsubscribed listener.
  • The subscribe method should return a function that allows the subscriber to easily unsubscribe themselves.
Loading editor...
javascript