Fixes infinite loop on record data update in command menu (#12072)
This PR fixes the infinite loop that was happening in `RecordShowEffect` component due to a useEffect on `recordStoreFamilyState` which was creating a non-deterministic open loop. The fix was to use a recoilCallback to avoid reading a stale state from Recoil with `useRecoilValue`, useRecoilCallback always gets the most fresh data and commits everything before React goes on with rendering, thus avoiding any stale value in a useEffect. Fixes https://github.com/twentyhq/twenty/issues/11079 Fixes https://github.com/twentyhq/core-team-issues/issues/957
This commit is contained in:
@ -2,11 +2,11 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { buildFindOneRecordForShowPageOperationSignature } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
|
||||
import { useSetRecordValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
type RecordShowEffectProps = {
|
||||
objectNameSingular: string;
|
||||
@ -19,6 +19,7 @@ export const RecordShowEffect = ({
|
||||
}: RecordShowEffectProps) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const setRecordValueInContextSelector = useSetRecordValue();
|
||||
|
||||
const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE =
|
||||
buildFindOneRecordForShowPageOperationSignature({
|
||||
@ -33,15 +34,25 @@ export const RecordShowEffect = ({
|
||||
withSoftDeleted: true,
|
||||
});
|
||||
|
||||
const [recordFromStore, setRecordFromStore] = useRecoilState(
|
||||
recordStoreFamilyState(recordId),
|
||||
const setRecordStore = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (newRecord: ObjectRecord | null | undefined) => {
|
||||
const previousRecordValue = snapshot
|
||||
.getLoadable(recordStoreFamilyState(recordId))
|
||||
.getValue();
|
||||
|
||||
if (JSON.stringify(previousRecordValue) !== JSON.stringify(newRecord)) {
|
||||
set(recordStoreFamilyState(recordId), newRecord);
|
||||
}
|
||||
|
||||
setRecordValueInContextSelector(recordId, newRecord);
|
||||
},
|
||||
[recordId, setRecordValueInContextSelector],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(record) && !isDeeplyEqual(record, recordFromStore)) {
|
||||
setRecordFromStore(record);
|
||||
}
|
||||
}, [record, recordFromStore, setRecordFromStore]);
|
||||
setRecordStore(record);
|
||||
}, [record, setRecordStore]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
useSetRecordValue,
|
||||
} from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
// TODO: should be optimized and put higher up
|
||||
export const RecordValueSetterEffect = ({ recordId }: { recordId: string }) => {
|
||||
@ -19,7 +18,10 @@ export const RecordValueSetterEffect = ({ recordId }: { recordId: string }) => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeeplyEqual(recordValueFromContextSelector, recordValueFromRecoil)) {
|
||||
if (
|
||||
JSON.stringify(recordValueFromContextSelector) !==
|
||||
JSON.stringify(recordValueFromRecoil)
|
||||
) {
|
||||
setRecordValueInContextSelector(recordId, recordValueFromRecoil);
|
||||
}
|
||||
}, [
|
||||
|
||||
@ -2,7 +2,7 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||
|
||||
export const recordStoreFamilyState = createFamilyState<
|
||||
ObjectRecord | null,
|
||||
ObjectRecord | null | undefined,
|
||||
string
|
||||
>({
|
||||
key: 'recordStoreFamilyState',
|
||||
|
||||
Reference in New Issue
Block a user