|
1 | 1 | import type { Json, PublicInterface } from '@metamask/utils';
|
2 | 2 | import { enablePatches, produceWithPatches, applyPatches, freeze } from 'immer';
|
3 | 3 | import type { Draft, Patch } from 'immer';
|
| 4 | +import { isEqual, get as getValueFromPath } from 'lodash'; |
4 | 5 |
|
5 | 6 | import type { ActionConstraint, EventConstraint } from './Messenger';
|
6 | 7 | import type {
|
@@ -244,6 +245,28 @@ export class BaseController<
|
244 | 245 | );
|
245 | 246 | }
|
246 | 247 |
|
| 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 | + |
247 | 270 | /**
|
248 | 271 | * Updates controller state. Accepts a callback that is passed a draft copy
|
249 | 272 | * of the controller state. If a value is returned, it is set as the new
|
@@ -272,7 +295,7 @@ export class BaseController<
|
272 | 295 | )(this.#internalState, callback);
|
273 | 296 |
|
274 | 297 | // Protect against unnecessary state updates when there is no state diff.
|
275 |
| - if (patches.length > 0) { |
| 298 | + if (patches.length > 0 && !this.#isSameState(patches)) { |
276 | 299 | this.#internalState = nextState;
|
277 | 300 | this.messagingSystem.publish(
|
278 | 301 | `${this.name}:stateChange`,
|
|
0 commit comments