Reactive State Management with Signals in React
Signals provide a fine-grained reactivity system, allowing components to react only to the specific state changes they depend on. This challenge asks you to implement a basic signals system within a React context, enabling efficient and targeted re-renders. This is a modern approach to state management, offering performance benefits over traditional methods like useState when dealing with complex state dependencies.
Problem Description
You are tasked with creating a createSignal function and a useSignal hook that implement a simple signals system. The createSignal function should take an initial value and return an object with two properties: value (the current signal value) and set (a function to update the signal value). The useSignal hook should accept a signal object and return the signal's value. When the set function is called, all components using the signal via useSignal should re-render.
Key Requirements:
createSignal(initialValue: T): { value: T; set: (newValue: T) => void }: Creates a new signal with the given initial value. Thesetfunction should accept a new value of the same typeT.useSignal<T>(signal: { value: T; set: (newValue: T) => void }): T: A React hook that subscribes to a signal and returns its current value. It should trigger a re-render of the component whenever the signal's value changes.- Reactivity: Components using
useSignalmust re-render only when the signal's value changes. - Type Safety: The signals system should be type-safe, ensuring that the
setfunction only accepts values of the correct type.
Expected Behavior:
- Calling
createSignalshould create a signal object with avalueproperty initialized to the providedinitialValueand asetfunction. - Calling the
setfunction should update the signal'svalueand trigger re-renders in all components usinguseSignalwith that signal. useSignalshould return the current value of the signal.- The system should be type-safe, preventing incorrect type assignments.
Edge Cases to Consider:
- Signals with primitive types (number, string, boolean).
- Signals with object types. (While this challenge doesn't require deep reactivity, consider how changes to properties within an object signal would be handled - a shallow comparison is sufficient for this exercise).
- Multiple components using the same signal.
- Updating a signal value with the same value it already holds. (Should not trigger a re-render).
Examples
Example 1:
Input:
createSignal(0); // Creates a signal named 'count' with initial value 0
const countSignal = createSignal(0);
// In a React component:
function MyComponent() {
const count = useSignal(countSignal);
return <div>Count: {count}</div>;
}
Output:
Initially, MyComponent renders "Count: 0".
When countSignal.set(1) is called, MyComponent re-renders to "Count: 1".
Explanation: createSignal creates a signal. useSignal subscribes to the signal. Updating the signal's value via set triggers a re-render of MyComponent.
Example 2:
Input:
const nameSignal = createSignal("Alice");
// In a React component:
function Greeting() {
const name = useSignal(nameSignal);
return <h1>Hello, {name}!</h1>;
}
nameSignal.set("Bob");
Output:
Initially, Greeting renders "Hello, Alice!".
After nameSignal.set("Bob"), Greeting re-renders to "Hello, Bob!".
Explanation: Similar to Example 1, but with a string signal.
Example 3: (Edge Case)
Input:
const isEnabledSignal = createSignal(true);
// In a React component:
function Toggle() {
const isEnabled = useSignal(isEnabledSignal);
return <button onClick={() => isEnabledSignal.set(!isEnabled)}>Toggle</button>;
}
// Initial render: isEnabledSignal.value is true.
// Clicking the button sets isEnabledSignal.value to false.
// Clicking the button again sets isEnabledSignal.value back to true.
Output: The component toggles between displaying the button state based on the signal's value. No unnecessary re-renders occur when setting the value back to its original state. Explanation: Demonstrates toggling a boolean signal and the importance of avoiding re-renders when the value remains unchanged.
Constraints
- React Version: Assume React 18 or later.
- Performance: The re-rendering should be efficient. Avoid unnecessary re-renders.
- Type Safety: The code must be written in TypeScript and maintain type safety.
- No External Libraries: Do not use any external state management libraries (e.g., Redux, Zustand). You are implementing a simplified signals system.
- Signal Creation Limit: Assume a maximum of 100 signals will be created concurrently.
Notes
- Consider using React's
useRefto store a list of components that are currently subscribed to a signal. - Think about how to efficiently trigger re-renders when a signal's value changes. Directly calling
setStateon each component might be inefficient. - Focus on the core functionality of creating signals and providing reactivity. Advanced features like derived signals or computed values are not required for this challenge.
- The goal is to demonstrate a basic understanding of how signals work and how they can be integrated into a React application.