Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code ba3e881dde fix: replace deep-equal with fast-deep-equal in isDeeplyEqual utility
https://sonarly.com/issue/19866?type=bug

The `deep-equal` v2.2.3 library crashes with `TypeError: iterator must be a function` when comparing record objects received via SSE update events on the companies page, causing an unhandled error during optimistic cache updates.

Fix: Replaced `deep-equal` v2.2.3 with `fast-deep-equal` in the `isDeeplyEqual` utility and all direct usages.

`deep-equal` has a complex dependency chain (`which-typed-array` → `for-each` → `is-callable`) that crashes with `TypeError: iterator must be a function` when encountering certain object shapes during type introspection. `fast-deep-equal` is a much simpler, faster alternative that handles plain JSON objects (which is all record data consists of) without the complex type-detection logic that causes this crash.

Changes:
1. **`isDeeplyEqual.ts`**: Replaced `deep-equal` import with `fast-deep-equal`. Removed the unused `options` parameter (only 2 callers passed `{ strict: true }`, and `fast-deep-equal` always uses strict comparison).
2. **`triggerUpdateRelationsOptimisticEffect.ts`**: Removed the `{ strict: true }` third argument from two `isDeeplyEqual` calls (no longer needed as `fast-deep-equal` is always strict).
3. **`useInitializeQueryParamState.ts`**: Replaced direct `deep-equal` import with `fast-deep-equal`.
2026-03-31 06:31:05 +00:00
3 changed files with 7 additions and 10 deletions
@@ -162,7 +162,6 @@ const triggerUpdateRelationOptimisticEffect = ({
const noDiff = isDeeplyEqual(
currentFieldValueOnSourceRecord,
updatedFieldValueOnSourceRecord,
{ strict: true },
);
if (noDiff && !isDeletion) {
return;
@@ -326,10 +325,9 @@ const triggerUpdateMorphRelationOptimisticEffect = ({
| null = updatedSourceRecord?.[gqlFieldMorphRelation];
const noDiff = isDeeplyEqual(
currentFieldValueOnSourceRecord,
updatedFieldValueOnSourceRecord,
{ strict: true },
);
currentFieldValueOnSourceRecord,
updatedFieldValueOnSourceRecord,
);
if (noDiff && !isDeletion) {
return;
}
@@ -5,7 +5,7 @@ import { returnToPathState } from '@/auth/states/returnToPathState';
import { type BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
import { isValidReturnToPath } from '@/auth/utils/isValidReturnToPath';
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/settings/billing/constants/BillingCheckoutSessionDefaultValue';
import deepEqual from 'deep-equal';
import fastDeepEqual from 'fast-deep-equal';
import { useStore } from 'jotai';
export const useInitializeQueryParamState = () => {
@@ -26,7 +26,7 @@ export const useInitializeQueryParamState = () => {
'plan' in parsedValue &&
'interval' in parsedValue &&
'requirePaymentMethod' in parsedValue &&
!deepEqual(billingCheckoutSession, parsedValue)
!fastDeepEqual(billingCheckoutSession, parsedValue)
) {
store.set(
billingCheckoutSessionState.atom,
@@ -1,4 +1,3 @@
import deepEqual from 'deep-equal';
import fastDeepEqual from 'fast-deep-equal';
export const isDeeplyEqual = <T>(a: T, b: T, options?: { strict: boolean }) =>
deepEqual(a, b, options);
export const isDeeplyEqual = <T>(a: T, b: T) => fastDeepEqual(a, b);