useHooks.iov4.1.2
DocsBlogGitHub
Hooks
No hooks found in any category.

useClosure

utility

Installation

npx usehooks-cli@latest add use-closure

Description

A React hook that provides utilities for creating and working with JavaScript closures. Closures are functions that have access to variables from their outer (enclosing) scope even after the outer function has returned. This hook demonstrates various closure patterns and provides practical utilities for managing private state, memoization, and encapsulation.

Return Type

UseClosureReturn
PropertyTypeDescription
createPrivateState<T>(initialValue: T) => PrivateState<T>Creates a closure with private state that can only be accessed through provided methods
createCounter(initialValue?: number) => CounterClosureCreates a classic counter closure with increment, decrement, and reset functionality
createMemoizer<T, R>(fn: (...args: T) => R) => (...args: T) => RCreates a memoization closure that caches function results
createModule<T>(initialState: T, methods: Record<string, Function>) => Record<string, Function>Creates a module pattern closure with private state and public methods
createEventEmitter() => EventEmitterClosureCreates an event emitter using closures for private listener management
createSecureState<T>(initialValue: T, validator?: (value: T) => boolean) => SecureStateClosure<T>Creates a secure state closure with validation and controlled access
demonstrateScope() => ScopeDemoDemonstrates how closures capture and maintain access to outer scope variables

Examples

Private State Management

Create private state that can only be modified through controlled methods

1const { createPrivateState } = useClosure(); 2 3const privateCounter = createPrivateState(0); 4 5// Only way to access the value 6console.log(privateCounter.get()); // 0 7 8// Controlled modification 9privateCounter.set(prev => prev + 1); 10console.log(privateCounter.get()); // 1 11 12// Reset to initial value 13privateCounter.reset();

Classic Counter Closure

Demonstrate the classic counter closure pattern

1const { createCounter } = useClosure(); 2 3const counter = createCounter(10); 4 5console.log(counter.increment()); // 11 6console.log(counter.increment()); // 12 7console.log(counter.decrement()); // 11 8console.log(counter.getValue()); // 11 9counter.reset(); // back to 10

Memoization with Closures

Create a memoized function using closures

1const { createMemoizer } = useClosure(); 2 3const expensiveFunction = (n: number) => { 4 console.log('Computing...'); 5 return n * n; 6}; 7 8const memoized = createMemoizer(expensiveFunction); 9 10console.log(memoized(5)); // Computing... 25 11console.log(memoized(5)); // 25 (cached, no "Computing...") 12console.log(memoized(3)); // Computing... 9

Module Pattern

Create a module with private state and public methods

1const { createModule } = useClosure(); 2 3const userModule = createModule( 4 { name: '', age: 0 }, 5 { 6 setName: (state, name: string) => { 7 state.name = name; 8 return `Name set to ${name}`; 9 }, 10 setAge: (state, age: number) => { 11 if (age >= 0) { 12 state.age = age; 13 return `Age set to ${age}`; 14 } 15 return 'Invalid age'; 16 }, 17 getInfo: (state) => `${state.name}, ${state.age} years old` 18 } 19); 20 21userModule.setName('John'); 22userModule.setAge(30); 23console.log(userModule.getInfo()); // "John, 30 years old"

Event Emitter with Closures

Create an event emitter using closure-based private state

1const { createEventEmitter } = useClosure(); 2 3const emitter = createEventEmitter(); 4 5// Subscribe to events 6const unsubscribe = emitter.on('test', (data) => { 7 console.log('Received:', data); 8}); 9 10// Emit events 11emitter.emit('test', 'Hello World!'); // Received: Hello World! 12 13// Unsubscribe 14unsubscribe();

Secure State with Validation

Create secure state with validation using closures

1const { createSecureState } = useClosure(); 2 3const secureAge = createSecureState( 4 25, 5 (age: number) => age >= 0 && age <= 150 6); 7 8console.log(secureAge.read()); // 25 9console.log(secureAge.write(30)); // true 10console.log(secureAge.write(-5)); // false (invalid) 11console.log(secureAge.read()); // 30 (unchanged from invalid write)

Scope Demonstration

Demonstrate how closures capture outer scope variables

1const { demonstrateScope } = useClosure(); 2 3const scope = demonstrateScope(); 4console.log(scope.outerVar); // "I'm in the outer scope" 5 6const innerFunction = scope.createInner('inner variable'); 7console.log(innerFunction()); 8// "I'm in the outer scope and inner variable (from inner scope)"

Dependencies

react

Notes

  • Closures maintain access to outer scope variables even after the outer function returns
  • This hook demonstrates various closure patterns: private state, counters, memoization, modules
  • Closures are useful for data privacy, encapsulation, and creating specialized functions
  • The module pattern uses closures to create objects with private state and public methods
  • Memoization closures capture the cache in their scope for persistent storage
  • Event emitters use closures to maintain private listener collections
  • All created closures maintain their own independent scope and state

Implementation

1"use client"; 2 3import { useCallback } from "react"; 4 5type ClosureFunction<T = any, R = any> = (...args: T[]) => R; 6type ClosureFactory<T = any, R = any> = ( 7 ...outerArgs: any[] 8) => ClosureFunction<T, R>; 9 10interface PrivateState<T> { 11 get: () => T; 12 set: (value: T | ((prev: T) => T)) => void; 13 reset: () => void; 14} 15 16interface UseClosureReturn { 17 // Create a closure with private state 18 createPrivateState: <T>(initialValue: T) => PrivateState<T>; 19 20 // Create a counter closure (classic example) 21 createCounter: (initialValue?: number) => { 22 increment: () => number; 23 decrement: () => number; 24 getValue: () => number; 25 reset: () => void; 26 }; 27 28 // Create a memoization closure 29 createMemoizer: <T extends any[], R>( 30 fn: (...args: T) => R 31 ) => (...args: T) => R; 32 33 // Create a closure with multiple private variables 34 createModule: <T extends Record<string, any>>( 35 initialState: T, 36 methods: Record<string, (state: T, ...args: any[]) => any> 37 ) => Record<string, (...args: any[]) => any>; 38 39 // Create a closure-based event emitter 40 createEventEmitter: () => { 41 on: (event: string, callback: Function) => () => void; 42 emit: (event: string, ...args: any[]) => void; 43 off: (event: string, callback?: Function) => void; 44 getListeners: (event: string) => Function[]; 45 }; 46 47 // Create a closure with access control 48 createSecureState: <T>( 49 initialValue: T, 50 validator?: (value: T) => boolean 51 ) => { 52 read: () => T; 53 write: (value: T) => boolean; 54 isValid: (value: T) => boolean; 55 }; 56 57 // Utility to demonstrate closure scope 58 demonstrateScope: () => { 59 outerVar: string; 60 createInner: (innerVar: string) => () => string; 61 }; 62} 63 64export function useClosure(): UseClosureReturn { 65 const createPrivateState = useCallback( 66 <T>(initialValue: T): PrivateState<T> => { 67 let privateValue = initialValue; 68 const originalValue = initialValue; 69 70 return { 71 get: () => privateValue, 72 set: (value: T | ((prev: T) => T)) => { 73 privateValue = 74 typeof value === "function" 75 ? (value as (prev: T) => T)(privateValue) 76 : value; 77 }, 78 reset: () => { 79 privateValue = originalValue; 80 }, 81 }; 82 }, 83 [] 84 ); 85 86 const createCounter = useCallback((initialValue: number = 0) => { 87 let count = initialValue; 88 const initial = initialValue; 89 90 return { 91 increment: () => ++count, 92 decrement: () => --count, 93 getValue: () => count, 94 reset: () => { 95 count = initial; 96 }, 97 }; 98 }, []); 99 100 const createMemoizer = useCallback( 101 <T extends any[], R>(fn: (...args: T) => R) => { 102 const cache = new Map<string, R>(); 103 104 return (...args: T): R => { 105 const key = JSON.stringify(args); 106 107 if (cache.has(key)) { 108 return cache.get(key)!; 109 } 110 111 const result = fn(...args); 112 cache.set(key, result); 113 return result; 114 }; 115 }, 116 [] 117 ); 118 119 const createModule = useCallback( 120 <T extends Record<string, any>>( 121 initialState: T, 122 methods: Record<string, (state: T, ...args: any[]) => any> 123 ) => { 124 let state = { ...initialState }; 125 const publicMethods: Record<string, (...args: any[]) => any> = {}; 126 127 // Create public methods that have access to private state 128 Object.keys(methods).forEach((methodName) => { 129 publicMethods[methodName] = (...args: any[]) => { 130 const method = methods[methodName]; 131 if (typeof method === "function") { 132 return method(state, ...args); 133 } 134 throw new Error(`Method ${methodName} is not defined`); 135 }; 136 }); 137 138 // Add state management methods 139 publicMethods.getState = () => ({ ...state }); 140 publicMethods.setState = (newState: Partial<T>) => { 141 state = { ...state, ...newState }; 142 }; 143 144 return publicMethods; 145 }, 146 [] 147 ); 148 149 const createEventEmitter = useCallback(() => { 150 const listeners = new Map<string, Set<Function>>(); 151 152 return { 153 on: (event: string, callback: Function) => { 154 if (!listeners.has(event)) { 155 listeners.set(event, new Set()); 156 } 157 listeners.get(event)!.add(callback); 158 159 // Return unsubscribe function (closure over callback) 160 return () => { 161 listeners.get(event)?.delete(callback); 162 }; 163 }, 164 165 emit: (event: string, ...args: any[]) => { 166 const eventListeners = listeners.get(event); 167 if (eventListeners) { 168 eventListeners.forEach((callback) => callback(...args)); 169 } 170 }, 171 172 off: (event: string, callback?: Function) => { 173 if (callback) { 174 listeners.get(event)?.delete(callback); 175 } else { 176 listeners.delete(event); 177 } 178 }, 179 180 getListeners: (event: string) => { 181 return Array.from(listeners.get(event) || []); 182 }, 183 }; 184 }, []); 185 186 const createSecureState = useCallback( 187 <T>(initialValue: T, validator?: (value: T) => boolean) => { 188 let secureValue = initialValue; 189 190 const isValid = (value: T): boolean => { 191 return validator ? validator(value) : true; 192 }; 193 194 return { 195 read: () => secureValue, 196 write: (value: T): boolean => { 197 if (isValid(value)) { 198 secureValue = value; 199 return true; 200 } 201 return false; 202 }, 203 isValid, 204 }; 205 }, 206 [] 207 ); 208 209 const demonstrateScope = useCallback(() => { 210 const outerVar = "I'm in the outer scope"; 211 212 return { 213 outerVar, 214 createInner: (innerVar: string) => { 215 // This function closes over both outerVar and innerVar 216 return () => `${outerVar} and ${innerVar} (from inner scope)`; 217 }, 218 }; 219 }, []); 220 221 return { 222 createPrivateState, 223 createCounter, 224 createMemoizer, 225 createModule, 226 createEventEmitter, 227 createSecureState, 228 demonstrateScope, 229 }; 230} 231