diff --git a/packages/use-store/src/experimental/Emitter.ts b/packages/use-store/src/experimental/Emitter.ts index 33953a1..a61080a 100644 --- a/packages/use-store/src/experimental/Emitter.ts +++ b/packages/use-store/src/experimental/Emitter.ts @@ -1,15 +1,17 @@ export default class Emitter> { - _listeners: Array<(...value: T) => void> = []; + _listeners: Set<(...value: T) => void> = new Set(); + subscribe(cb: (...value: T) => void): () => void { - const wrapped = (...value: T) => cb(...value); - this._listeners.push(wrapped); + this._listeners.add(cb); + return () => { - this._listeners = this._listeners.filter((s) => s !== wrapped); + this._listeners.delete(cb); }; } + notify(...value: T) { - this._listeners.forEach((cb) => { + for (const cb of this._listeners) { cb(...value); - }); + } } } diff --git a/packages/use-store/src/experimental/testUseCases/reduxUseCase.spec.tsx b/packages/use-store/src/experimental/testUseCases/reduxUseCase.spec.tsx index 0e52e59..0966c71 100644 --- a/packages/use-store/src/experimental/testUseCases/reduxUseCase.spec.tsx +++ b/packages/use-store/src/experimental/testUseCases/reduxUseCase.spec.tsx @@ -211,6 +211,6 @@ describe("createStore for Redux", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); }); diff --git a/packages/use-store/src/experimental/useStore.spec.tsx b/packages/use-store/src/experimental/useStore.spec.tsx index b8462ec..d00785f 100644 --- a/packages/use-store/src/experimental/useStore.spec.tsx +++ b/packages/use-store/src/experimental/useStore.spec.tsx @@ -159,7 +159,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("Does not tear when new component mounts in its own transition mid transition", async () => { @@ -252,7 +252,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("Does not miss updates triggered in useEffect or useLayoutEffect", async () => { @@ -386,7 +386,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); // This should catch the case where fixups accidentally could get entangled with a transition when they should flush sync. @@ -552,7 +552,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("Sync update interrupting transition correctly tracks committed state", async () => { @@ -684,7 +684,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("Multiple sync updates interrupting transition correctly tracks committed state", async () => { @@ -818,7 +818,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("flushSync update interrupting transition correctly tracks committed state", async () => { @@ -946,7 +946,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("correctly handles consecutive sync updates", async () => { @@ -995,7 +995,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("dynamic stores are not yet supported", async () => { @@ -1048,8 +1048,8 @@ describe("Experimental Userland Store", () => { "useStoreSelector does not currently support dynamic stores", ); unmount(); - expect(store1._listeners.length).toBe(0); - expect(store2._listeners.length).toBe(0); + expect(store1._listeners.size).toBe(0); + expect(store2._listeners.size).toBe(0); }); it("transition store update causes new store reader to mount", async () => { @@ -1104,7 +1104,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); // Describes a limitation of our fixup logic: If a component mounts sync @@ -1244,7 +1244,7 @@ describe("Experimental Userland Store", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("can read from multiple different stores updating independently", async () => { @@ -1311,8 +1311,8 @@ describe("Experimental Userland Store", () => { unmount(); - expect(storeA._listeners.length).toBe(0); - expect(storeB._listeners.length).toBe(0); + expect(storeA._listeners.size).toBe(0); + expect(storeB._listeners.size).toBe(0); }); }); @@ -1366,7 +1366,7 @@ describe("Selectors can be dynamic", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); it("selector changes sync during a transition update to the store", async () => { @@ -1453,6 +1453,6 @@ describe("Selectors can be dynamic", () => { `); unmount(); - expect(store._listeners.length).toBe(0); + expect(store._listeners.size).toBe(0); }); });