fix: enable save button when changing currency default value (#16864)

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 <felix.malfait@gmail.com>
This commit is contained in:
Abdullah.
2026-01-06 23:02:43 +05:00
committed by GitHub
parent 971ddc0bb4
commit a46619ec55
3 changed files with 53 additions and 16 deletions
@@ -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<SettingsDataModelFieldCurrencyFormValues>();
@@ -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<SettingsDataModelFieldEditFormValues>({
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,
},
});
@@ -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 };
};