Skip to content

Commit

Permalink
fix(map): trigger onDelete for replacing same key
Browse files Browse the repository at this point in the history
  • Loading branch information
crimx committed Jun 20, 2024
1 parent 428b0ce commit a956c0a
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 19 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ npm add value-enhancer
| `{ compute }` | 192 B |
| `{ flattenFrom }` | 227 B |
| `{ flatten }` | 36 B |
| `{ reactiveMap }` | 479 B |
| `{ reactiveMap }` | 464 B |
| `{ reactiveSet }` | 359 B |
| `{ reactiveList }` | 528 B |

Expand Down
36 changes: 18 additions & 18 deletions src/collections/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export type ReadonlyReactiveMap<TKey, TValue> = Pick<
readonly $: ReadonlyVal<ReadonlyReactiveMap<TKey, TValue>>;
};

export interface ReactiveMapConfig<TKey, TValue> {
export interface ReactiveMapConfig<TValue> {
/**
* A callback function that will be called when an entry is deleted.
*
Expand All @@ -69,7 +69,7 @@ export interface ReactiveMapConfig<TKey, TValue> {
* - `map.clear()` is called.
* - `map.dispose()` is called.
*/
onDeleted?: (value: TValue, key: TKey) => void;
onDeleted?: (value: TValue) => void;
}

class ReactiveMapImpl<TKey, TValue>
Expand All @@ -78,7 +78,7 @@ class ReactiveMapImpl<TKey, TValue>
{
public constructor(
entries?: Iterable<readonly [TKey, TValue]> | null,
config?: ReactiveMapConfig<TKey, TValue>
config?: ReactiveMapConfig<TValue>
) {
super();

Expand All @@ -101,14 +101,14 @@ class ReactiveMapImpl<TKey, TValue>

#notify: () => void;

#onDeleted?: (value: TValue, key: TKey) => void;
#onDeleted?: (value: TValue) => void;

#delete(key: TKey): boolean {
if (this.#onDeleted) {
if (super.has(key)) {
const value = super.get(key)!;
super.delete(key);
this.#onDeleted(value, key);
this.#onDeleted(value);
return true;
}
return false;
Expand All @@ -118,10 +118,10 @@ class ReactiveMapImpl<TKey, TValue>

#clear(): void {
if (this.#onDeleted) {
const deleted = [...this];
const deleted = new Set(this.values());
super.clear();
for (const [key, value] of deleted) {
this.#onDeleted(value, key);
for (const value of deleted) {
this.#onDeleted(value);
}
} else {
super.clear();
Expand Down Expand Up @@ -162,7 +162,7 @@ class ReactiveMapImpl<TKey, TValue>
}
super.set(key, value);
if (this.#onDeleted) {
this.#onDeleted(oldValue, key);
this.#onDeleted(oldValue);
}
} else {
super.set(key, value);
Expand Down Expand Up @@ -190,21 +190,21 @@ class ReactiveMapImpl<TKey, TValue>

public replace(entries: Iterable<readonly [TKey, TValue]>): Iterable<TValue> {
const oldMap = new Map(this);
const deleted = new Map<TKey, TValue>(this);
let isDirty = false;
const deleted = new Set<TValue>(this.values());
let hasNewValue = false;
super.clear();

for (const [key, value] of entries) {
isDirty =
isDirty || !oldMap.has(key) || !Object.is(oldMap.get(key), value);
super.set(key, value);
deleted.delete(key);
deleted.delete(value);
hasNewValue =
hasNewValue || !oldMap.has(key) || !Object.is(oldMap.get(key), value);
}

if (isDirty || deleted.size > 0) {
if (hasNewValue || deleted.size > 0) {
if (this.#onDeleted) {
for (const [key, value] of deleted) {
this.#onDeleted(value, key);
for (const value of deleted) {
this.#onDeleted(value);
}
}
this.#notify();
Expand Down Expand Up @@ -257,5 +257,5 @@ class ReactiveMapImpl<TKey, TValue>
*/
export const reactiveMap = <TKey, TValue>(
entries?: Iterable<readonly [TKey, TValue]> | null,
config?: ReactiveMapConfig<TKey, TValue>
config?: ReactiveMapConfig<TValue>
): ReactiveMap<TKey, TValue> => new ReactiveMapImpl(entries, config);
17 changes: 17 additions & 0 deletions test/collections/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ describe("ReactiveMap", () => {
expect(map.get("baz")).toEqual(3);
});

it("should return deleted same key entries", () => {
const map = reactiveMap(Object.entries({ foo: 1, bar: 2 }));
const deleted = map.replace(Object.entries({ foo: 3, bar: 4 }));
expect([...deleted]).toHaveLength(2);
});

it("should notify on replace", () => {
const map = reactiveMap<string, number>();
const mockNotify = jest.fn();
Expand Down Expand Up @@ -380,6 +386,17 @@ describe("ReactiveMap", () => {
expect(map.size).toBe(0);
expect(spy).toHaveBeenCalledTimes(1);
});

it("should trigger one onDelete for same value different keys", () => {
const spy = jest.fn();
const map = reactiveMap(Object.entries({ foo: 2, bar: 2 }), {
onDeleted: spy,
});
const deleted = map.replace(Object.entries({ foo: 3, bar: 4 }));
expect([...deleted]).toHaveLength(1);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(2);
});
});

describe("toJSON", () => {
Expand Down

0 comments on commit a956c0a

Please sign in to comment.