Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issues 1980 and 2003 #2057

Merged
merged 2 commits into from
Jul 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 5.13.0 / 4.13.0

* Fixed bug that observable map replace function breaks entities order. related issues:[#1980](https://github.com/mobxjs/mobx/issues/1980) and [#2003](https://github.com/mobxjs/mobx/issues/2003)
* Fixed potential memory leak in observable maps, when non-primitive values are used as keys. Fixes [#2031](https://github.com/mobxjs/mobx/issues/2031) through [#2032](https://github.com/mobxjs/mobx/pull/2032).
* Added support to store additional non-observable(!) fields (string or symbol based) on array, to better reflect behavior of MobX 4. Fixes [#2044](https://github.com/mobxjs/mobx/issues/2044) through [#2046](https://github.com/mobxjs/mobx/pull/2046)

Expand Down
25 changes: 19 additions & 6 deletions src/types/observablemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
createInstanceofPredicate,
deepEnhancer,
fail,
getMapLikeKeys,
getNextId,
getPlainObjectKeys,
hasInterceptors,
Expand All @@ -32,7 +31,8 @@ import {
transaction,
untracked,
onBecomeUnobserved,
globalState
globalState,
convertToMap
} from "../internal"

export interface IKeyValueMap<V = any> {
Expand Down Expand Up @@ -335,11 +335,24 @@ export class ObservableMap<K = any, V = any>
// grab all the keys that are present in the new map but not present in the current map
// and delete them from the map, then merge the new map
// this will cause reactions only on changed values
const newKeys = (getMapLikeKeys(values) as any) as K[]
const replacementMap = convertToMap(values)
const orderedData = new Map()
const oldKeys = Array.from(this.keys())
const missingKeys = oldKeys.filter(k => newKeys.indexOf(k) === -1)
missingKeys.forEach(k => this.delete(k))
this.merge(values)

for (let i = 0; i < oldKeys.length; i++) {
const oldKey = oldKeys[i]
// delete missing key
if (!replacementMap.has(oldKey)) {
this.delete(oldKey)
}
}
// set new keys in the right order
replacementMap.forEach((value, key) => {
this.set(key, value)
orderedData.set(key, this._data.get(key))
})
// use data with correct order
this._data = orderedData
})
return this
}
Expand Down
12 changes: 12 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ export function isPlainObject(value) {
return proto === Object.prototype || proto === null
}

export function convertToMap(dataStructure) {
if (isES6Map(dataStructure) || isObservableMap(dataStructure)) {
return dataStructure
} else if (Array.isArray(dataStructure)) {
return new Map(dataStructure)
} else if (isPlainObject(dataStructure)) {
return new Map(Object.entries(dataStructure))
} else {
return fail(`Cannot convert to map from '${dataStructure}'`)
}
}

export function makeNonEnumerable(object: any, propNames: PropertyKey[]) {
for (let i = 0; i < propNames.length; i++) {
addHiddenProp(object, propNames[i], object[propNames[i]])
Expand Down
13 changes: 12 additions & 1 deletion test/base/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -618,11 +618,22 @@ test("issue 1243, .replace should not trigger change on unchanged values", () =>

expect(() => {
m.replace("not-an-object")
}).toThrow(/Cannot get keys from 'not-an-object'/)
}).toThrow(/Cannot convert to map from 'not-an-object'/)

d()
})

test("#1980 .replace should not breaks entities order!", () => {
const original = mobx.observable.map([["a", "first"], ["b", "second"]])
const replacement = new Map([["b", "first"], ["a", "second"]])
original.replace(replacement)
const newKeys = Array.from(replacement)
const originalKeys = Array.from(replacement)
for (let i = 0; i < newKeys.length; i++) {
expect(newKeys[i]).toEqual(originalKeys[i])
}
})

test("#1258 cannot replace maps anymore", () => {
const items = mobx.observable.map()
items.replace(mobx.observable.map())
Expand Down