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`.
67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
import { useCallback } from 'react';
|
|
|
|
import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState';
|
|
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 fastDeepEqual from 'fast-deep-equal';
|
|
import { useStore } from 'jotai';
|
|
|
|
export const useInitializeQueryParamState = () => {
|
|
const store = useStore();
|
|
const initializeQueryParamState = useCallback(() => {
|
|
const handlers: Record<string, (value: string) => void> = {
|
|
billingCheckoutSession: (value: string) => {
|
|
const billingCheckoutSession = store.get(
|
|
billingCheckoutSessionState.atom,
|
|
);
|
|
|
|
try {
|
|
const parsedValue = JSON.parse(decodeURIComponent(value));
|
|
|
|
if (
|
|
typeof parsedValue === 'object' &&
|
|
parsedValue !== null &&
|
|
'plan' in parsedValue &&
|
|
'interval' in parsedValue &&
|
|
'requirePaymentMethod' in parsedValue &&
|
|
!fastDeepEqual(billingCheckoutSession, parsedValue)
|
|
) {
|
|
store.set(
|
|
billingCheckoutSessionState.atom,
|
|
parsedValue as BillingCheckoutSession,
|
|
);
|
|
}
|
|
} catch (error) {
|
|
// oxlint-disable-next-line no-console
|
|
console.error(
|
|
'Failed to parse billingCheckoutSession from URL',
|
|
error,
|
|
);
|
|
store.set(
|
|
billingCheckoutSessionState.atom,
|
|
BILLING_CHECKOUT_SESSION_DEFAULT_VALUE,
|
|
);
|
|
}
|
|
},
|
|
returnToPath: (value: string) => {
|
|
if (isValidReturnToPath(value)) {
|
|
store.set(returnToPathState.atom, value);
|
|
}
|
|
},
|
|
};
|
|
|
|
const queryParams = new URLSearchParams(window.location.search);
|
|
|
|
for (const [paramName, handler] of Object.entries(handlers)) {
|
|
const value = queryParams.get(paramName);
|
|
if (value !== null) {
|
|
handler(value);
|
|
}
|
|
}
|
|
}, [store]);
|
|
|
|
return { initializeQueryParamState };
|
|
};
|