Skip to content

Commit 31f8723

Browse files
authored
Fixes for ReactiveSet (#688)
2 parents 8d7529b + 32fcb81 commit 31f8723

7 files changed

Lines changed: 129 additions & 59 deletions

File tree

.changeset/long-squids-report.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@solid-primitives/set": minor
3+
---
4+
5+
Fixes for ReactiveSet (#688):
6+
- Iterators and `.forEach()` no longer track specific keys.
7+
- Added support for `thisArg` as per `forEach` spec
8+
- `super.clear()` now called before dirtying signals
9+
- Uses new `dirtyAll` form trigger package

.changeset/swift-points-dance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@solid-primitives/trigger": minor
3+
---
4+
5+
Add `dirtyAll` to `createTriggerCache`

packages/set/src/index.ts

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,69 +23,76 @@ export class ReactiveSet<T> extends Set<T> {
2323
if (values) for (const v of values) super.add(v);
2424
}
2525

26-
// reads
26+
[Symbol.iterator](): SetIterator<T> {
27+
return this.values();
28+
}
29+
2730
get size(): number {
2831
this.#triggers.track($KEYS);
2932
return super.size;
3033
}
31-
has(v: T): boolean {
32-
this.#triggers.track(v);
33-
return super.has(v);
34+
35+
has(value: T): boolean {
36+
this.#triggers.track(value);
37+
return super.has(value);
3438
}
3539

36-
*keys(): SetIterator<T> {
37-
for (const key of super.keys()) {
38-
this.#triggers.track(key);
39-
yield key;
40-
}
41-
this.#triggers.track($KEYS);
40+
keys(): SetIterator<T> {
41+
return this.values();
4242
}
43-
values(): SetIterator<T> {
44-
return this.keys();
43+
44+
*values(): SetIterator<T> {
45+
this.#triggers.track($KEYS);
46+
47+
for (const value of super.values()) {
48+
yield value;
49+
}
4550
}
51+
4652
*entries(): SetIterator<[T, T]> {
47-
for (const key of super.keys()) {
48-
this.#triggers.track(key);
49-
yield [key, key];
50-
}
5153
this.#triggers.track($KEYS);
52-
}
5354

54-
[Symbol.iterator](): SetIterator<T> {
55-
return this.values();
55+
for (const entry of super.entries()) {
56+
yield entry;
57+
}
5658
}
57-
forEach(callbackfn: (value: T, value2: T, set: this) => void) {
59+
60+
forEach(callbackfn: (value1: T, value2: T, set: Set<T>) => void, thisArg?: any): void {
5861
this.#triggers.track($KEYS);
59-
super.forEach(callbackfn as any);
62+
super.forEach(callbackfn, thisArg);
6063
}
6164

62-
// writes
63-
add(v: T): this {
64-
if (!super.has(v)) {
65-
super.add(v);
65+
add(value: T): this {
66+
if (!super.has(value)) {
67+
super.add(value);
6668
batch(() => {
67-
this.#triggers.dirty(v);
69+
this.#triggers.dirty(value);
6870
this.#triggers.dirty($KEYS);
6971
});
7072
}
73+
7174
return this;
7275
}
73-
delete(v: T): boolean {
74-
const r = super.delete(v);
75-
if (r) {
76+
77+
delete(value: T): boolean {
78+
const result = super.delete(value);
79+
80+
if (result) {
7681
batch(() => {
77-
this.#triggers.dirty(v);
82+
this.#triggers.dirty(value);
7883
this.#triggers.dirty($KEYS);
7984
});
8085
}
81-
return r;
86+
87+
return result;
8288
}
89+
8390
clear(): void {
8491
if (super.size) {
92+
super.clear();
93+
8594
batch(() => {
86-
for (const v of super.keys()) this.#triggers.dirty(v);
87-
super.clear();
88-
this.#triggers.dirty($KEYS);
95+
this.#triggers.dirtyAll();
8996
});
9097
}
9198
}
@@ -109,21 +116,25 @@ export class ReactiveWeakSet<T extends object> extends WeakSet<T> {
109116
if (values) for (const v of values) super.add(v);
110117
}
111118

112-
has(v: T): boolean {
113-
this.#triggers.track(v);
114-
return super.has(v);
119+
has(value: T): boolean {
120+
this.#triggers.track(value);
121+
return super.has(value);
115122
}
116-
add(v: T): this {
117-
if (!super.has(v)) {
118-
super.add(v);
119-
this.#triggers.dirty(v);
123+
124+
add(value: T): this {
125+
if (!super.has(value)) {
126+
super.add(value);
127+
this.#triggers.dirty(value);
120128
}
121129
return this;
122130
}
123-
delete(v: T): boolean {
124-
const deleted = super.delete(v);
125-
deleted && this.#triggers.dirty(v);
126-
return deleted;
131+
132+
delete(value: T): boolean {
133+
const result = super.delete(value);
134+
135+
result && this.#triggers.dirty(value);
136+
137+
return result;
127138
}
128139
}
129140

packages/set/test/index.test.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,33 +100,39 @@ describe("ReactiveSet", () => {
100100
const dispose = createRoot(dispose => {
101101
createEffect(() => {
102102
const run: number[] = [];
103-
let i = 0;
104103
for (const key of fn(set)) {
105104
run.push(key);
106-
if (i === 2) break; // don't iterate over all keys
107-
i += 1;
108105
}
109106
captured.push(run);
110107
});
111108
return dispose;
112109
});
113110

114111
expect(captured).toHaveLength(1);
115-
expect(captured[0]).toEqual([1, 2, 3]);
112+
expect(captured[0]).toEqual([1, 2, 3, 4]);
116113

117114
set.delete(4);
118-
expect(captured, "deleted unseen key").toHaveLength(1);
115+
expect(captured).toHaveLength(2);
116+
expect(captured[1]).toEqual([1, 2, 3]);
119117

120118
set.delete(1);
121-
expect(captured, "deleted seen").toHaveLength(2);
122-
expect(captured[1]).toEqual([2, 3]);
119+
expect(captured).toHaveLength(3);
120+
expect(captured[2]).toEqual([2, 3]);
123121

124122
set.add(4);
125-
expect(captured, "added key in reach").toHaveLength(3);
126-
expect(captured[2]).toEqual([2, 3, 4]);
123+
expect(captured).toHaveLength(4);
124+
expect(captured[3]).toEqual([2, 3, 4]);
127125

128126
set.add(5);
129-
expect(captured, "added key out of reach").toHaveLength(3);
127+
expect(captured).toHaveLength(5);
128+
expect(captured[4]).toEqual([2, 3, 4, 5]);
129+
130+
set.add(5);
131+
expect(captured).toHaveLength(5);
132+
133+
set.clear();
134+
expect(captured).toHaveLength(6);
135+
expect(captured[5]).toEqual([]);
130136

131137
dispose();
132138
});

packages/trigger/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ map.dirty(1);
7575
map.dirty(2);
7676
```
7777

78+
### Triggering all keys
79+
80+
`dirtyAll` will trigger all keys in the cache.
81+
82+
```ts
83+
const [track, _dirty, dirtyAll] = createTriggerCache<number>();
84+
85+
createEffect(() => {
86+
track(1);
87+
// ...
88+
});
89+
90+
// later (will trigger the update)
91+
dirtyAll();
92+
```
93+
7894
### Weak version
7995

8096
`createTriggerCache` constructor can take a `WeakMap` constructor as an argument. This will create a `WeakMap` of triggers instead of a `Map`.

packages/trigger/src/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export class TriggerCache<T> {
5050
this.#map.get(key)?.$$();
5151
}
5252

53+
dirtyAll() {
54+
if (isServer) return;
55+
for (const trigger of this.#map.values()) trigger.$$();
56+
}
57+
5358
track(key: T) {
5459
if (!getListener()) return;
5560
let trigger = this.#map.get(key);
@@ -77,11 +82,12 @@ export class TriggerCache<T> {
7782
* Trigger signals added to the cache only when tracked under a computation,
7883
* and get deleted from the cache when they are no longer tracked.
7984
*
80-
* @returns a tuple of `[track, dirty]` functions
85+
* @returns a tuple of `[track, dirty, dirtyAll]` functions
8186
*
8287
* `track` and `dirty` are called with a `key` so that each tracker will trigger an update only when his individual `key` would get marked as dirty.
88+
* `dirtyAll` will mark all keys as dirty and trigger an update for all of them.
8389
* @example
84-
* const [track, dirty] = createTriggerCache()
90+
* const [track, dirty, dirtyAll] = createTriggerCache()
8591
* createEffect(() => {
8692
* track(1)
8793
* ...
@@ -90,10 +96,12 @@ export class TriggerCache<T> {
9096
* dirty(1)
9197
* // this won't cause an update:
9298
* dirty(2)
99+
* // this will cause an update to all keys:
100+
* dirtyAll()
93101
*/
94102
export function createTriggerCache<T>(
95103
mapConstructor: WeakMapConstructor | MapConstructor = Map,
96-
): [track: (key: T) => void, dirty: (key: T) => void] {
104+
): [track: (key: T) => void, dirty: (key: T) => void, dirtyAll: () => void] {
97105
const map = new TriggerCache(mapConstructor);
98-
return [map.track.bind(map), map.dirty.bind(map)];
106+
return [map.track.bind(map), map.dirty.bind(map), map.dirtyAll.bind(map)];
99107
}

packages/trigger/test/index.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ describe("createTriggerCache", () => {
2222
dispose();
2323
}));
2424

25+
test("dirtyAll", () =>
26+
createRoot(dispose => {
27+
const [track, , dirtyAll] = createTriggerCache(Map);
28+
let runs = -1;
29+
createComputed(() => {
30+
track(1);
31+
runs++;
32+
});
33+
expect(runs).toBe(0);
34+
dirtyAll();
35+
expect(runs).toBe(1);
36+
37+
dispose();
38+
}));
39+
2540
test("weak trigger cache", () =>
2641
createRoot(dispose => {
2742
const [track, dirty] = createTriggerCache();

0 commit comments

Comments
 (0)