From a46619ec5586b9fedbfc1d3f2f606f2e45b3e7d2 Mon Sep 17 00:00:00 2001 From: "Abdullah." <125115953+mabdullahabaid@users.noreply.github.com> Date: Tue, 6 Jan 2026 23:02:43 +0500 Subject: [PATCH] fix: enable save button when changing currency default value (#16864) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #16792 ## Problem When editing a currency field’s default value (for example, changing from **USD** to **UYU**), the **Save** button remained disabled. However, if the format setting was changed first (**short → full → short**), the Save button would then work correctly for currency changes. ## Root Cause The form was using **React Hook Form’s `values` mode**, which did not properly track dirty state for the `defaultValue` and `settings` fields. ## Solution Switched to the **`defaultValues` + `reset()`** pattern: - Initialize the form with `defaultValues` (using placeholder values) - Call `reset()` when `fieldMetadataItem` loads This correctly tracks dirty state by comparing against the most recent `reset` values. ## Note A similar pattern is used in: - `useWebhookForm` - `useImapSmtpCaldavConnectionForm` Those hooks call `reset()` via query callbacks. In this case, `reset()` is called inside a `useEffect` because the data comes from synchronous Recoil state rather than an async query. Found two solutions to the problem, created commits for both. I feel the useEffect pattern makes more sense here since it's cleaner in terms of code. --------- Co-authored-by: Félix Malfait --- .../useCurrencySettingsFormInitialValues.ts | 27 +++++++------- .../data-model/SettingsObjectFieldEdit.tsx | 7 +++- .../getFieldMetadataItemInitialValues.ts | 35 +++++++++++++++++++ 3 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/getFieldMetadataItemInitialValues.ts diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues.ts index e0cd4d9376..576a71ca84 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues.ts @@ -2,11 +2,10 @@ import { useFormContext } from 'react-hook-form'; import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById'; import { type SettingsDataModelFieldCurrencyFormValues } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencyForm'; -import { isNonEmptyString } from '@sniptt/guards'; import { CurrencyCode } from 'twenty-shared/constants'; +import { getFieldMetadataItemInitialValues } from '~/pages/settings/data-model/utils/getFieldMetadataItemInitialValues'; import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/formatNumber'; import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString'; -import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString'; type UseCurrencySettingsFormInitialValuesArgs = { existingFieldMetadataId: string; @@ -18,25 +17,23 @@ export const useCurrencySettingsFormInitialValues = ({ existingFieldMetadataId, ); - const initialAmountMicrosValue = - (fieldMetadataItem?.defaultValue?.amountMicros as number | null) ?? null; - const initialCurrencyCodeValue = isNonEmptyString( - stripSimpleQuotesFromString(fieldMetadataItem?.defaultValue?.currencyCode), - ) - ? fieldMetadataItem?.defaultValue?.currencyCode - : applySimpleQuotesToString(CurrencyCode.USD); + const { settings, defaultValue } = + getFieldMetadataItemInitialValues(fieldMetadataItem); const initialFormValues: SettingsDataModelFieldCurrencyFormValues = { - settings: { - format: fieldMetadataItem?.settings?.format ?? 'short', - decimals: fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE, + settings: settings ?? { + format: 'short', + decimals: DEFAULT_DECIMAL_VALUE, }, - defaultValue: { - amountMicros: initialAmountMicrosValue, - currencyCode: initialCurrencyCodeValue, + defaultValue: defaultValue ?? { + amountMicros: null, + currencyCode: applySimpleQuotesToString(CurrencyCode.USD), }, }; + const initialAmountMicrosValue = initialFormValues.defaultValue.amountMicros; + const initialCurrencyCodeValue = initialFormValues.defaultValue.currencyCode; + const { resetField } = useFormContext(); diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx index 3c5f199e6a..a3a2af21d0 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx @@ -43,6 +43,7 @@ import { Section } from 'twenty-ui/layout'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getFieldMetadataItemInitialValues } from '~/pages/settings/data-model/utils/getFieldMetadataItemInitialValues'; //TODO: fix this type export type SettingsDataModelFieldEditFormValues = z.infer< @@ -107,6 +108,9 @@ export const SettingsObjectFieldEdit = () => { const getRelationMetadata = useGetRelationMetadata(); const { updateOneFieldMetadataItem } = useUpdateOneFieldMetadataItem(); + const { settings, defaultValue } = + getFieldMetadataItemInitialValues(fieldMetadataItem); + const formConfig = useForm({ mode: 'onTouched', resolver: zodResolver(settingsFieldFormSchema()), @@ -116,7 +120,8 @@ export const SettingsObjectFieldEdit = () => { label: fieldMetadataItem?.label ?? '', description: fieldMetadataItem?.description, isLabelSyncedWithName: fieldMetadataItem?.isLabelSyncedWithName ?? true, - settings: fieldMetadataItem?.settings, + settings, + defaultValue, }, }); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/getFieldMetadataItemInitialValues.ts b/packages/twenty-front/src/pages/settings/data-model/utils/getFieldMetadataItemInitialValues.ts new file mode 100644 index 0000000000..7747a19f73 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/utils/getFieldMetadataItemInitialValues.ts @@ -0,0 +1,35 @@ +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { isNonEmptyString } from '@sniptt/guards'; +import { CurrencyCode } from 'twenty-shared/constants'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/formatNumber'; +import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString'; + +export const getFieldMetadataItemInitialValues = ( + fieldMetadataItem: FieldMetadataItem | undefined, +) => { + if (fieldMetadataItem?.type !== FieldMetadataType.CURRENCY) { + return { + settings: fieldMetadataItem?.settings ?? undefined, + defaultValue: fieldMetadataItem?.defaultValue ?? undefined, + }; + } + + const settings = fieldMetadataItem.settings ?? { + format: 'short', + decimals: DEFAULT_DECIMAL_VALUE, + }; + + const currencyCode = isNonEmptyString( + fieldMetadataItem.defaultValue?.currencyCode, + ) + ? fieldMetadataItem.defaultValue?.currencyCode + : applySimpleQuotesToString(CurrencyCode.USD); + + const defaultValue = { + amountMicros: fieldMetadataItem.defaultValue?.amountMicros ?? null, + currencyCode, + }; + + return { settings, defaultValue }; +};