// Bug: state is shared across all instances of the hook
const sharedCache = { count: 0, label: '' };
function useCounter(label) {
sharedCache.label = label; // shared — last writer wins!
return {
get: () => sharedCache.count,
inc: () => { sharedCache.count++; },
label: () => sharedCache.label,
};
}
const a = useCounter('counterA');
const b = useCounter('counterB');
a.inc(); a.inc();
b.inc();
console.log(a.get()); // should be 2
console.log(b.get()); // should be 1
console.log(a.label()); // should be counterAfunction useCounter(label) {
// Each call gets its own private state via closure
let count = 0;
const savedLabel = label;
return {
get: () => count,
inc: () => { count++; },
label: () => savedLabel,
};
}
const a = useCounter('counterA');
const b = useCounter('counterB');
a.inc(); a.inc();
b.inc();
console.log(a.get());
console.log(b.get());
console.log(a.label());Bug: sharedCache is declared outside the hook — a module-level singleton. Both a and b share the same object. b.inc() increments the same count. a.label() reads counterB because b was initialized last.
Explanation: Each useCounter() call creates a fresh closure with its own count and savedLabel variables. a and b are completely independent.
Key Insight: Custom hooks share LOGIC, not STATE. Every call to a custom hook creates independent state. Module-level variables are singletons — never use them for per-instance state.