Skip to content

Commit

Permalink
fix: Fix max stack errors on cycles.
Browse files Browse the repository at this point in the history
Replaces fast-deep-equal with a copy/pasted/inlined version of a
cycle-aware deepEquals.
  • Loading branch information
stephenh committed Oct 17, 2024
1 parent 2212001 commit 6391d42
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 6 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"src/**/*.{ts,tsx,css,md}": "prettier --write"
},
"dependencies": {
"fast-deep-equal": "^3.1.3",
"is-plain-object": "^5.0.0"
},
"peerDependencies": {
Expand Down
40 changes: 40 additions & 0 deletions src/fields/deepEquals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Our own `deepEquals` because either `fast-deep-equals` or `dequal` or etc actually
* handle cyclic data structures, despite ChatGTP's assertions/hallucinations.
*
* Ported from https://github.com/KoryNunn/cyclic-deep-equal which is ISC.
*/
export function deepEquals(a: any, b: any, visited: Set<any> = new Set()): boolean {
const aType = typeof a;
if (aType !== typeof b) return false;

if (a == null || b == null || !(aType === "object" || aType === "function")) {
if (aType === "number" && isNaN(a) && isNaN(b)) return true;
return a === b;
}

if (Array.isArray(a) !== Array.isArray(b)) return false;

const aKeys = Object.keys(a),
bKeys = Object.keys(b);
if (aKeys.length !== bKeys.length) return false;

let equal = true;

for (const key of aKeys) {
if (!(key in b)) {
equal = false;
break;
}
if (a[key] && a[key] instanceof Object) {
if (visited.has(a[key])) break;
visited.add(a[key]);
}
if (!deepEquals(a[key], b[key], visited)) {
equal = false;
break;
}
}

return equal;
}
20 changes: 20 additions & 0 deletions src/formState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,26 @@ describe("formState", () => {
expect(a.value).toEqual({ address: { street: "123", city: "nyc" } });
});

it("can have child object states with cycles", () => {
// Given an author with an address child
// And two addresses that are basically identical and both have cycles
const address1: AuthorAddress = { city: "city2" };
(address1 as any).someCycle = address1;
const address2: AuthorAddress = { city: "city2" };
(address2 as any).someCycle = address2;
const a = createObjectState<AuthorInput>(
{
id: { type: "value" },
address: { type: "value" },
},
{ address: address1 },
);
// When we change the address
a.address.set(address2);
// Then it doesn't error on dirty checks
expect(a.address.dirty).toBe(false);
});

it("provides isNewEntity", () => {
// Given an author without an id
const formState = createObjectState(authorWithAddressConfig, {
Expand Down
8 changes: 4 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import equal from "fast-deep-equal";
import { isPlainObject } from "is-plain-object";
import { isObservable, toJS } from "mobx";
import { ListFieldConfig, ObjectConfig, ObjectFieldConfig, ValueFieldConfig } from "src/config";
import { deepEquals } from "src/fields/deepEquals";
import { InputAndMap, QueryAndMap, UseFormStateOpts } from "src/useFormState";

export type Builtin = Date | Function | Uint8Array | string | number | boolean;
Expand Down Expand Up @@ -124,16 +124,16 @@ export function isEmpty(value: any): boolean {
*/
export function areEqual<T>(a?: T, b?: T, strictOrder?: boolean): boolean {
if (isPlainObject(a)) {
return equal(toJS(a), toJS(b));
return deepEquals(toJS(a), toJS(b));
}
if (hasToJSON(a) || hasToJSON(b)) {
const a1 = hasToJSON(a) ? a.toJSON() : a;
const b1 = hasToJSON(b) ? b.toJSON() : b;
return equal(a1, b1);
return deepEquals(a1, b1);
}
if (a && b && a instanceof Array && b instanceof Array) {
if (strictOrder !== false) {
return equal(a, b);
return deepEquals(a, b);
}
if (a.length !== b.length) return false;
return a.every((a1) => b.some((b1) => areEqual(a1, b1)));
Expand Down
1 change: 0 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2843,7 +2843,6 @@ __metadata:
eslint-plugin-jsx-a11y: ^6.4.1
eslint-plugin-react: ^7.22.0
eslint-plugin-react-hooks: ^4.2.0
fast-deep-equal: ^3.1.3
husky: ^3.1.0
is-plain-object: ^5.0.0
jest: ^29.7.0
Expand Down

0 comments on commit 6391d42

Please sign in to comment.