Implementing a Custom Hook System in JavaScript
This challenge asks you to build a simplified custom hook system in JavaScript, mimicking the core functionality of React's hooks but without relying on React itself. Custom hooks are a powerful way to extract component logic into reusable functions, making your code more organized and maintainable. This exercise will help you understand the underlying principles of hooks and how they manage state and side effects.
Problem Description
You need to implement a createHook function that acts as a factory for creating custom hooks. Each custom hook should maintain its own internal state and provide a set of functions for managing that state. The createHook function should accept an initial state object as an argument and return an object containing:
state: The current state object.setState: A function that accepts a new state object or a function that receives the current state and returns a new state object. This function should update the internal state and trigger any registered update functions (explained below).subscribe: A function that accepts a callback function. This callback should be invoked whenever the state changes. The callback receives the new state as an argument.unsubscribe: A function that accepts the callback function returned bysubscribeand removes it from the list of subscribers.
The system should handle both direct state updates (passing a new state object) and functional updates (passing a function that receives the current state and returns a new state object). It should also correctly manage multiple subscribers and ensure they are notified of state changes.
Examples
Example 1:
Input: createHook({ count: 0 })
Output: {
state: { count: 0 },
setState: (newState) => { ... },
subscribe: (callback) => { ... },
unsubscribe: (callback) => { ... }
}
Explanation: The `createHook` function returns an object with the initial state and the necessary functions for managing it.
Example 2:
const myHook = createHook({ name: "Alice", age: 30 });
const updateCallback = (newState) => {
console.log("State updated:", newState);
};
const subscription = myHook.subscribe(updateCallback);
myHook.setState({ age: 31 }); // Output: State updated: { name: "Alice", age: 31 }
myHook.setState((prevState) => ({ ...prevState, name: "Bob" })); // Output: State updated: { name: "Bob", age: 31 }
myHook.unsubscribe(subscription);
myHook.setState({ age: 32 }); // No output, as the callback is unsubscribed
Explanation: Demonstrates subscribing to state changes, updating state directly and functionally, and unsubscribing from updates.
Example 3: (Edge Case - Multiple Subscribers)
const hook = createHook({ value: 10 });
const callback1 = (newState) => console.log("Callback 1:", newState);
const callback2 = (newState) => console.log("Callback 2:", newState);
const sub1 = hook.subscribe(callback1);
const sub2 = hook.subscribe(callback2);
hook.setState(20); // Output: Callback 1: { value: 20 }, Callback 2: { value: 20 }
hook.unsubscribe(sub1);
hook.setState(30); // Output: Callback 2: { value: 30 }
Explanation: Shows that multiple subscribers are correctly notified of state changes, and unsubscribing one subscriber doesn't affect others.
Constraints
- The
setStatefunction should handle both direct state updates (object) and functional updates (function). - The
subscribefunction should return a unique identifier (e.g., a function reference) that can be used withunsubscribe. - The
unsubscribefunction should only remove the specific callback provided, not all callbacks. - The initial state must be an object.
- The state updates should be merged correctly, ensuring that existing properties are not overwritten unintentionally when using functional updates.
Notes
- You don't need to worry about performance optimizations like memoization or batching updates. Focus on the core functionality of state management and subscription.
- Consider using closures to encapsulate the state and subscription logic within the custom hook.
- Think about how to ensure that the
unsubscribefunction correctly removes the callback from the list of subscribers. Using the callback function itself as the identifier is a common approach. - This is a simplified implementation; React's hooks have additional features and optimizations. The goal is to understand the fundamental concepts.