Repository: solidjs-community/solid-primitives
Pull request #784 (2025-04-02 20:04:25)

Make ReactiveSet.clear() and ReactiveMap.clear() more efficient by only notifying listeners of existing members
Listeners of non-existing members cannot observe different values before or after `.clear()` `.get(nonexsting) -> undefined` or `.has(nonexisting) -> false` so there's no use in notifying them.

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 19e90912f1cae731087f47647a992dbd20579de1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/webstrand/solid-primitives/new/main?filename=.changeset/metal-forks-grin.md&value=---%0A%22%40solid-primitives%2Fmap%22%3A%20patch%0A%22%40solid-primitives%2Fset%22%3A%20patch%0A---%0A%0AMake%20ReactiveSet.clear()%20and%20ReactiveMap.clear()%20more%20efficient%20by%20only%20notifying%20listeners%20of%20existing%20members%0A)


atk: This is still missing a changeset. Run `npx changeset` in the project root.

Patch:
diff --git a/.changeset/funny-baths-drive.md b/.changeset/funny-baths-drive.md
new file mode 100644
index 00000000..2b33ca47
--- /dev/null
+++ b/.changeset/funny-baths-drive.md
@@ -0,0 +1,5 @@
+---
+"@solid-primitives/set": minor
+---
+
+Avoid notifying non-existent members on Set.clear()
diff --git a/.changeset/red-islands-tickle.md b/.changeset/red-islands-tickle.md
new file mode 100644
index 00000000..2d1d8f28
--- /dev/null
+++ b/.changeset/red-islands-tickle.md
@@ -0,0 +1,5 @@
+---
+"@solid-primitives/map": minor
+---
+
+Avoid notifying non-existent members on Map.clear()
diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts
index 489c5bc7..eeba7a34 100644
--- a/packages/map/src/index.ts
+++ b/packages/map/src/index.ts
@@ -120,14 +120,17 @@ export class ReactiveMap<K, V> extends Map<K, V> {
   }
 
   clear(): void {
-    if (super.size) {
-      super.clear();
+    if (super.size === 0) return;
+    batch(() => {
+      this.#keyTriggers.dirty($OBJECT);
+      this.#valueTriggers.dirty($OBJECT);
+      for (const key of super.keys()) {
+        this.#keyTriggers.dirty(key);
+        this.#valueTriggers.dirty(key);
+      }
 
-      batch(() => {
-        this.#keyTriggers.dirtyAll();
-        this.#valueTriggers.dirtyAll();
-      });
-    }
+      super.clear();
+    });
   }
 }
 
diff --git a/packages/set/src/index.ts b/packages/set/src/index.ts
index 26d81653..b26c6496 100644
--- a/packages/set/src/index.ts
+++ b/packages/set/src/index.ts
@@ -88,13 +88,14 @@ export class ReactiveSet<T> extends Set<T> {
   }
 
   clear(): void {
-    if (super.size) {
+    if (!super.size) return;
+    batch(() => {
+      this.#triggers.dirty($KEYS);
+      for (const member of super.values()) {
+        this.#triggers.dirty(member);
+      }
       super.clear();
-
-      batch(() => {
-        this.#triggers.dirtyAll();
-      });
-    }
+    });
   }
 }
 


Test patch:
diff --git a/packages/map/test/index.test.ts b/packages/map/test/index.test.ts
index ccc5f289..6c9b84ca 100644
--- a/packages/map/test/index.test.ts
+++ b/packages/map/test/index.test.ts
@@ -378,6 +378,45 @@ describe("ReactiveMap", () => {
 
     dispose();
   });
+  test(".clear() notifies only listeners of existing members", () =>
+    createRoot(dispose => {
+      const map = new ReactiveMap([
+        [1, "a"],
+        [2, "b"],
+        [3, "c"],
+      ]);
+
+      const existingKey = vi.fn();
+      createComputed(() => existingKey(map.has(2)));
+
+      const existingValue = vi.fn();
+      createComputed(() => existingValue(map.get(2)));
+
+      const nonexistingKey = vi.fn();
+      createComputed(() => nonexistingKey(map.has(4)));
+
+      const nonexistingValue = vi.fn();
+      createComputed(() => nonexistingValue(map.get(4)));
+
+      expect(existingKey).toHaveBeenNthCalledWith(1, true);
+      expect(existingValue).toHaveBeenNthCalledWith(1, "b");
+
+      expect(nonexistingKey).toHaveBeenNthCalledWith(1, false);
+      expect(nonexistingValue).toHaveBeenNthCalledWith(1, undefined);
+
+      map.clear();
+
+      expect(existingKey).toHaveBeenCalledTimes(2);
+      expect(existingKey).toHaveBeenNthCalledWith(2, false);
+
+      expect(existingValue).toHaveBeenCalledTimes(2);
+      expect(existingValue).toHaveBeenNthCalledWith(2, undefined);
+
+      expect(nonexistingKey).toHaveBeenCalledTimes(1);
+      expect(nonexistingValue).toHaveBeenCalledTimes(1);
+
+      dispose();
+    }));
 });
 
 describe("ReactiveWeakMap", () => {
diff --git a/packages/set/test/index.test.ts b/packages/set/test/index.test.ts
index 412fba98..e7e46c67 100644
--- a/packages/set/test/index.test.ts
+++ b/packages/set/test/index.test.ts
@@ -182,6 +182,29 @@ describe("ReactiveSet", () => {
 
     dispose();
   });
+
+  test("clear notifies only listeners of existing members", () =>
+    createRoot(dispose => {
+      const set = new ReactiveSet([1, 2, 3, 4]);
+
+      const existing = vi.fn();
+      createComputed(() => existing(set.has(2)));
+
+      const nonexisting = vi.fn();
+      createComputed(() => nonexisting(set.has(5)));
+
+      expect(existing).toHaveBeenNthCalledWith(1, true);
+      expect(nonexisting).toHaveBeenNthCalledWith(1, false);
+
+      set.clear();
+
+      expect(existing).toHaveBeenCalledTimes(2);
+      expect(existing).toHaveBeenNthCalledWith(2, false);
+
+      expect(nonexisting).toHaveBeenCalledTimes(1);
+
+      dispose();
+    }));
 });
 
 describe("ReactiveWeakSet", () => {