Skip to content

Commit 0a0b188

Browse files
committed
Add use-set
1 parent 017987a commit 0a0b188

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed

hooks/use-set.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { type SetStateAction, useState, useEffect } from 'react'
2+
3+
/**
4+
* This is a set that causes rerenders on updates
5+
* @note Effects and memos that use this set should also listen for its signal: `+INSTANCE`
6+
*/
7+
export class StatefulSet<T> extends Set<T> {
8+
/** The dispatch function for the signal */
9+
private readonly _dispatchSignal?: React.Dispatch<SetStateAction<number>>
10+
/** THe dispatch function for redefining the set */
11+
private _dispatchRedefine?: React.Dispatch<SetStateAction<StatefulSet<T>>>
12+
13+
/**
14+
* Construct a StatefulSet
15+
* @param initial The initial value (parameter for a vanilla set)
16+
* @param dispatchSignal The dispatch function for the signal
17+
*/
18+
constructor (initial?: Iterable<T>, dispatchSignal?: StatefulSet<T>['_dispatchSignal']) {
19+
super(initial)
20+
this._dispatchSignal = dispatchSignal
21+
}
22+
23+
/**
24+
* Set the redefine dispatch
25+
* @private
26+
* @param callback The function
27+
*/
28+
_setRedefine (callback: StatefulSet<T>['_dispatchRedefine']): void {
29+
this._dispatchRedefine = callback
30+
}
31+
32+
/**
33+
* Force a signal update
34+
*/
35+
forceUpdate (): void {
36+
this._dispatchSignal?.(-1)
37+
}
38+
39+
/**
40+
* Set the instance to an entirely new instance
41+
* @param value The new instance
42+
* @returns The new instance
43+
* @throws {Error} If there's no redefinition callback defined
44+
*/
45+
reset (value: Iterable<T>): Iterable<T> {
46+
if (!this._dispatchRedefine) throw new Error('Cannot redefine Set. No redefine callback set.')
47+
const instance = new StatefulSet(value, this._dispatchSignal)
48+
49+
this._dispatchRedefine(instance)
50+
instance._dispatchSignal?.(instance.size)
51+
52+
return instance
53+
}
54+
55+
/**
56+
* @override
57+
*/
58+
override add (value: T): this {
59+
super.add(value)
60+
this._dispatchSignal?.(super.size)
61+
return this
62+
}
63+
64+
/**
65+
* @override
66+
*/
67+
override delete (value: T): boolean {
68+
const returnValue = super.delete(value)
69+
this._dispatchSignal?.(super.size)
70+
return returnValue
71+
}
72+
73+
/**
74+
* @override
75+
*/
76+
override clear (): void {
77+
super.clear()
78+
this._dispatchSignal?.(super.size)
79+
}
80+
81+
/**
82+
* Toggle if an element is present within the set
83+
* @note This is a custom set method
84+
* @param value The value to toggle
85+
* @returns The new state: true if the value is now in the set, false if the value is now not in the set
86+
*/
87+
toggle (value: T): boolean {
88+
if (super.has(value)) {
89+
super.delete(value)
90+
this._dispatchSignal?.(super.size)
91+
return false
92+
} else {
93+
super.add(value)
94+
this._dispatchSignal?.(super.size)
95+
return false
96+
}
97+
}
98+
99+
/**
100+
* Returns the set's signal. Used for effects and memos that use this set
101+
* @returns A numeric signal
102+
*/
103+
override valueOf (): number {
104+
return this.size
105+
}
106+
}
107+
108+
/**
109+
* Use a stately set
110+
* @note Any effects or memos that use this set should also listen for its signal (`+INSTANCE`)
111+
* @param initial The initial set value
112+
* @returns The stately set
113+
*/
114+
export function useSet<T> (initial?: Set<T> | T[]): StatefulSet<T> {
115+
const [, setSignal] = useState(Array.isArray(initial) ? initial.length : initial?.size ?? 0)
116+
const [set, setSet] = useState(new StatefulSet(initial, setSignal))
117+
118+
useEffect(() => set._setRedefine(setSet), [set])
119+
120+
return set
121+
}

0 commit comments

Comments
 (0)