From b8d85e0b269d6f7034654ee13cfc5deeb147f99c Mon Sep 17 00:00:00 2001
From: Clayton <118192227+claytonlin1110@users.noreply.github.com>
Date: Fri, 30 Jan 2026 11:12:43 -0600
Subject: [PATCH] fix: prevent default object re-selection in relation field
form (#17313)
Fixes #17111
### Problem
When creating a Relation field, after deselecting the pre-selected
default object (e.g., Company) and selecting a different object (e.g.,
Opportunity), both objects would end up being selected, showing "2
Objects" instead of just the newly selected one.
### Root Cause
The `initialMorphRelationsObjectMetadataIds` array was being recreated
on every component render, causing react-hook-form's `Controller` to
treat the `defaultValue` prop as a new value and re-apply it after user
changes.
### Solution
1. **Memoized the initial value**: Used `useMemo` to ensure
`initialMorphRelationsObjectMetadataIds` has a stable reference across
renders
2. **Added initialization guard**: Used `useEffect` with a `useRef` flag
to ensure the default value is only set once during component mount
3. **Maintained Controller defaultValue**: Kept the `defaultValue` prop
on the Controller, which now works correctly with the stable memoized
value
### Changes
- `SettingsDataModelFieldRelationForm.tsx`: Added memoization and
initialization guard to prevent default value re-application
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
---
.../SettingsMorphRelationMultiSelect.tsx | 4 +++-
.../SettingsDataModelFieldRelationForm.tsx | 13 +++++++----
.../input/components/MultiSelectControl.tsx | 23 +++++++++++++------
3 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/packages/twenty-front/src/modules/settings/components/SettingsMorphRelationMultiSelect.tsx b/packages/twenty-front/src/modules/settings/components/SettingsMorphRelationMultiSelect.tsx
index ceb89c27a8..9c274fa069 100644
--- a/packages/twenty-front/src/modules/settings/components/SettingsMorphRelationMultiSelect.tsx
+++ b/packages/twenty-front/src/modules/settings/components/SettingsMorphRelationMultiSelect.tsx
@@ -1,5 +1,5 @@
import styled from '@emotion/styled';
-import { plural } from '@lingui/core/macro';
+import { plural, t } from '@lingui/core/macro';
import { useMemo, useRef, useState, type MouseEvent } from 'react';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@@ -191,6 +191,7 @@ export const SettingsMorphRelationMultiSelect = ({
isDisabled={isDisabled}
selectSizeVariant={selectSizeVariant}
hasRightElement={hasRightElement}
+ placeholderText={t`Select objects...`}
/>
) : (
}
dropdownComponents={
diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationForm.tsx
index 2637b36181..2110f45ff7 100644
--- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationForm.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationForm.tsx
@@ -17,6 +17,7 @@ import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
+import { useMemo } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { RelationType } from '~/generated-metadata/graphql';
@@ -114,10 +115,14 @@ export const SettingsDataModelFieldRelationForm = ({
relationType: initialRelationType,
});
- const initialMorphRelationsObjectMetadataIds =
- initialRelationObjectMetadataItems.map(
- (relationObjectMetadataItem) => relationObjectMetadataItem.id,
- );
+ const initialMorphRelationsObjectMetadataIds = useMemo(
+ () =>
+ initialRelationObjectMetadataItems.map(
+ (relationObjectMetadataItem) => relationObjectMetadataItem.id,
+ ),
+ [initialRelationObjectMetadataItems],
+ );
+
const isMobile = useIsMobile();
return (
diff --git a/packages/twenty-front/src/modules/ui/input/components/MultiSelectControl.tsx b/packages/twenty-front/src/modules/ui/input/components/MultiSelectControl.tsx
index 4c75faa35e..af59e7df7c 100644
--- a/packages/twenty-front/src/modules/ui/input/components/MultiSelectControl.tsx
+++ b/packages/twenty-front/src/modules/ui/input/components/MultiSelectControl.tsx
@@ -20,6 +20,7 @@ type MultiSelectControlProps = Omit & {
fixedIcon?: IconComponent;
fixedText?: string;
selectedOptions: MultiSelectOptionType[];
+ placeholderText?: string;
};
export const MultiSelectControl = ({
@@ -30,16 +31,22 @@ export const MultiSelectControl = ({
selectSizeVariant,
textAccent = 'default',
hasRightElement,
+ placeholderText,
}: MultiSelectControlProps) => {
const theme = useTheme();
- const firstSelectedOption = selectedOptions?.[0];
+ const firstSelectedOption = selectedOptions[0];
+ const hasSelection = selectedOptions.length > 0;
+
return (
{isDefined(fixedIcon) ? (
@@ -48,7 +55,7 @@ export const MultiSelectControl = ({
size: theme.icon.size.md,
stroke: theme.icon.stroke.sm,
})
- ) : isDefined(firstSelectedOption?.Icon) ? (
+ ) : hasSelection && isDefined(firstSelectedOption.Icon) ? (
- ) : (
-
- )}
+ ) : hasSelection ? (
+
+ ) : isDefined(placeholderText) ? (
+
+ ) : null}