oncePerKey
Creates a function that ensures the callback is only called once per unique key. Includes a .reset() method to clear the internal call tracking.
1/**
2 * Creates a function that ensures the callback is only called once per unique key.
3 * Includes a .reset() method to clear the internal call tracking.
4 *
5 * @param fn - The function to call.
6 * @returns A function that takes a key and calls `fn` only once per unique key.
7 */
8export function oncePerKey<K = string>(
9 fn: (key: K) => void
10): ((key: K) => void) & { reset: () => void } {
11 const called = new Set<K>();
12
13 const wrapper = (key: K) => {
14 if (!called.has(key)) {
15 called.add(key);
16 fn(key);
17 }
18 };
19
20 wrapper.reset = () => {
21 called.clear();
22 };
23
24 return wrapper;
25}
Per-Key Execution Control
Ensures that the callback is only invoked once for each unique key, providing granular, key-specific memoization.
Efficient Set-Based Tracking
Uses a
Set
to efficiently store and check previously seen keys with O(1) performance.Resettable Behavior
Includes a
.reset()
method to clear the internal state, allowing re-use of the same instance in new cycles or contexts.Flexible Key Typing
Supports custom key types via a generic type parameter (
K
), improving type safety and adaptability.
Tests | Examples
1test('oncePerKey - calls function only once per key', () => {
2 const spy = jest.fn();
3 const once = oncePerKey(spy);
4
5 once('a');
6 once('a');
7 once('b');
8 once('b');
9
10 expect(spy).toHaveBeenCalledTimes(2);
11 expect(spy).toHaveBeenCalledWith('a');
12 expect(spy).toHaveBeenCalledWith('b');
13});
14
15test('oncePerKey - reset() allows keys to be reused', () => {
16 const spy = jest.fn();
17 const once = oncePerKey(spy);
18
19 once('x');
20 once('x');
21
22 once.reset();
23
24 once('x');
25 expect(spy).toHaveBeenCalledTimes(2);
26});
27
28test('oncePerKey - works with numbers as keys', () => {
29 const spy = jest.fn();
30 const once = oncePerKey<number>(spy);
31
32 once(1);
33 once(1);
34 once(2);
35 expect(spy).toHaveBeenCalledTimes(2);
36});
Common Use Cases
Logging or Analytics Deduplication
Ensure certain events (e.g., per user, per session, per item) are tracked only once.
Notification or Alert Control
Show warnings or messages only once per context (e.g., per field, per resource, per route).
One-Time Setup per Resource
Initialize or load something once per unique identifier (e.g., per tab, component, or entity).
Testing and Mocking
Limit the execution of mock callbacks to avoid multiple invocations per key during test runs.
Memoization-like Control
Create side-effect behavior that runs only once per parameter (e.g., feature flag activation or event registration).