Skip to content

Commit cd5c2df

Browse files
committed
perf: check for state diff before firing :stateChange
1 parent ded28ee commit cd5c2df

File tree

3 files changed

+27
-2
lines changed

3 files changed

+27
-2
lines changed

packages/base-controller/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
},
4848
"dependencies": {
4949
"@metamask/utils": "^11.2.0",
50-
"immer": "^9.0.6"
50+
"immer": "^9.0.6",
51+
"lodash": "^4.17.21"
5152
},
5253
"devDependencies": {
5354
"@metamask/auto-changelog": "^3.4.4",

packages/base-controller/src/BaseControllerV2.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Json, PublicInterface } from '@metamask/utils';
22
import { enablePatches, produceWithPatches, applyPatches, freeze } from 'immer';
33
import type { Draft, Patch } from 'immer';
4+
import { isEqual, get as getValueFromPath } from 'lodash';
45

56
import type { ActionConstraint, EventConstraint } from './Messenger';
67
import type {
@@ -244,6 +245,28 @@ export class BaseController<
244245
);
245246
}
246247

248+
/**
249+
* Checks if the list of patches would produce the same state (current state).
250+
*
251+
* @param patches - List of state patches.
252+
* @returns True if the patches would produce the same state, false otherwise.
253+
*/
254+
#isSameState(patches: Patch[]) {
255+
for (const { op, path, value: newValue } of patches) {
256+
if (op === 'replace') {
257+
const oldValue = getValueFromPath(this.state, path);
258+
259+
if (!isEqual(oldValue, newValue)) {
260+
return false;
261+
}
262+
} else {
263+
// Any other operation would really update the state (removing/adding).
264+
return false;
265+
}
266+
}
267+
return true;
268+
}
269+
247270
/**
248271
* Updates controller state. Accepts a callback that is passed a draft copy
249272
* of the controller state. If a value is returned, it is set as the new
@@ -272,7 +295,7 @@ export class BaseController<
272295
)(this.#internalState, callback);
273296

274297
// Protect against unnecessary state updates when there is no state diff.
275-
if (patches.length > 0) {
298+
if (patches.length > 0 && !this.#isSameState(patches)) {
276299
this.#internalState = nextState;
277300
this.messagingSystem.publish(
278301
`${this.name}:stateChange`,

yarn.lock

+1
Original file line numberDiff line numberDiff line change
@@ -2681,6 +2681,7 @@ __metadata:
26812681
deepmerge: "npm:^4.2.2"
26822682
immer: "npm:^9.0.6"
26832683
jest: "npm:^27.5.1"
2684+
lodash: "npm:^4.17.21"
26842685
sinon: "npm:^9.2.4"
26852686
ts-jest: "npm:^27.1.4"
26862687
typedoc: "npm:^0.24.8"

0 commit comments

Comments
 (0)