fix: prevent duplicate dropdowns in activity targets when editing in different contexts (#12462)
Closes #12361 ### Changes Made - In `ActivityTargetsInlineCell`, we now use different component instance IDs based on context. This ensures that each instance of the component (whether in right drawer or main view) has its own isolated state, preventing state conflicts and duplicate dropdowns. - The `MultipleRecordPicker` component now properly resets its state when closed, preventing state leakage between instances. https://github.com/user-attachments/assets/deb99687-a803-417e-a339-cab061026739
This commit is contained in:
@ -0,0 +1,11 @@
|
||||
import { useIsInRightDrawerOrThrow } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
|
||||
export const useActivityTargetsComponentInstanceId = (
|
||||
baseComponentInstanceId: string,
|
||||
) => {
|
||||
const { isInRightDrawer } = useIsInRightDrawerOrThrow();
|
||||
|
||||
return isInRightDrawer
|
||||
? `${baseComponentInstanceId}-right-drawer`
|
||||
: baseComponentInstanceId;
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { useActivityTargetsComponentInstanceId } from '@/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId';
|
||||
import { Note } from '@/activities/types/Note';
|
||||
import { getActivityPreview } from '@/activities/utils/getActivityPreview';
|
||||
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
||||
@ -72,6 +73,11 @@ export const NoteCard = ({
|
||||
|
||||
const body = getActivityPreview(note?.bodyV2?.blocknote ?? null);
|
||||
|
||||
const baseComponentInstanceId = `note-card-${note.id}-targets`;
|
||||
const componentInstanceId = useActivityTargetsComponentInstanceId(
|
||||
baseComponentInstanceId,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledCard isSingleNote={isSingleNote}>
|
||||
<StyledCardDetailsContainer
|
||||
@ -93,7 +99,7 @@ export const NoteCard = ({
|
||||
fieldPosition={0}
|
||||
>
|
||||
<ActivityTargetsInlineCell
|
||||
componentInstanceId={`note-card-${note.id}-targets`}
|
||||
componentInstanceId={componentInstanceId}
|
||||
activityRecordId={note.id}
|
||||
activityObjectNameSingular={CoreObjectNameSingular.Note}
|
||||
/>
|
||||
|
||||
@ -6,6 +6,7 @@ import { getActivitySummary } from '@/activities/utils/getActivitySummary';
|
||||
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
|
||||
|
||||
import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||
import { useActivityTargetsComponentInstanceId } from '@/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
@ -81,6 +82,11 @@ export const TaskRow = ({ task }: { task: Task }) => {
|
||||
|
||||
const { completeTask } = useCompleteTask(task);
|
||||
|
||||
const baseComponentInstanceId = `task-row-targets-${task.id}`;
|
||||
const componentInstanceId = useActivityTargetsComponentInstanceId(
|
||||
baseComponentInstanceId,
|
||||
);
|
||||
|
||||
return (
|
||||
<ActivityRow
|
||||
onClick={() => {
|
||||
@ -131,7 +137,7 @@ export const TaskRow = ({ task }: { task: Task }) => {
|
||||
activityRecordId={task.id}
|
||||
showLabel={false}
|
||||
maxWidth={200}
|
||||
componentInstanceId={`task-row-targets-${task.id}`}
|
||||
componentInstanceId={componentInstanceId}
|
||||
/>
|
||||
</StopPropagationContainer>
|
||||
</FieldContextProvider>
|
||||
|
||||
@ -2,6 +2,7 @@ import { MultipleRecordPickerItemsDisplay } from '@/object-record/record-picker/
|
||||
import { MultipleRecordPickerOnClickOutsideEffect } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerOnClickOutsideEffect';
|
||||
import { MultipleRecordPickerSearchInput } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerSearchInput';
|
||||
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
|
||||
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
||||
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
|
||||
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
|
||||
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
|
||||
@ -60,12 +61,37 @@ export const MultipleRecordPicker = ({
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const multipleRecordPickerPickableMorphItemsState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
multipleRecordPickerPickableMorphItemsComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const resetState = useRecoilCallback(
|
||||
({ set }) => {
|
||||
return () => {
|
||||
set(multipleRecordPickerPickableMorphItemsState, []);
|
||||
set(multipleRecordPickerSearchFilterState, '');
|
||||
};
|
||||
},
|
||||
[
|
||||
multipleRecordPickerPickableMorphItemsState,
|
||||
multipleRecordPickerSearchFilterState,
|
||||
],
|
||||
);
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit?.();
|
||||
goBackToPreviousHotkeyScope();
|
||||
resetSelectedItem();
|
||||
resetState();
|
||||
};
|
||||
|
||||
const handleClickOutside = () => {
|
||||
onClickOutside();
|
||||
resetState();
|
||||
};
|
||||
|
||||
useScopedHotkeys(
|
||||
@ -108,7 +134,7 @@ export const MultipleRecordPicker = ({
|
||||
>
|
||||
<MultipleRecordPickerOnClickOutsideEffect
|
||||
containerRef={containerRef}
|
||||
onClickOutside={onClickOutside}
|
||||
onClickOutside={handleClickOutside}
|
||||
/>
|
||||
<DropdownContent ref={containerRef}>
|
||||
{layoutDirection === 'search-bar-on-bottom' && (
|
||||
|
||||
@ -20,6 +20,7 @@ import { RecordDetailDuplicatesSection } from '@/object-record/record-show/recor
|
||||
import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
|
||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||
import { useIsInRightDrawerOrThrow } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
type FieldsCardProps = {
|
||||
@ -48,6 +49,8 @@ export const FieldsCard = ({
|
||||
objectRecordId,
|
||||
});
|
||||
|
||||
const { isInRightDrawer } = useIsInRightDrawerOrThrow();
|
||||
|
||||
const availableFieldMetadataItems = objectMetadataItem.fields
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
@ -139,7 +142,9 @@ export const FieldsCard = ({
|
||||
componentInstanceId={getRecordFieldInputId(
|
||||
objectRecordId,
|
||||
fieldMetadataItem.name,
|
||||
'fields-card',
|
||||
isInRightDrawer
|
||||
? 'right-drawer-fields-card'
|
||||
: 'fields-card',
|
||||
)}
|
||||
activityObjectNameSingular={
|
||||
objectNameSingular as
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ShowPageContainer } from '@/ui/layout/page/components/ShowPageContainer';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
|
||||
import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord';
|
||||
|
||||
@ -49,7 +50,7 @@ export const RecordShowContainer = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<RecordShowContainerContextStoreTargetedRecordsEffect
|
||||
recordId={objectRecordId}
|
||||
/>
|
||||
@ -71,6 +72,6 @@ export const RecordShowContainer = ({
|
||||
loading={isPrefetchLoading || loading || recordLoading}
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</>
|
||||
</RightDrawerProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { createRequiredContext } from '~/utils/createRequiredContext';
|
||||
|
||||
type RightDrawerContextType = {
|
||||
isInRightDrawer: boolean;
|
||||
};
|
||||
|
||||
export const [RightDrawerProvider, useIsInRightDrawerOrThrow] =
|
||||
createRequiredContext<RightDrawerContextType>('RightDrawer');
|
||||
Reference in New Issue
Block a user