Skip to content

Commit 2fe6d0d

Browse files
authored
Fix : keep modification dialogs dirty when switching nodes in sync mode (#3303)
Signed-off-by: Ayoub LABIDI <[email protected]>
1 parent 35d1ad6 commit 2fe6d0d

File tree

12 files changed

+47
-33
lines changed

12 files changed

+47
-33
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { FieldValues, useForm, UseFormProps, UseFormReturn } from 'react-hook-form';
9+
10+
export function useFormWithDirtyTracking<T extends FieldValues>(props: UseFormProps<T>): UseFormReturn<T> {
11+
const methods = useForm<T>(props);
12+
// Destructure isDirty to ensure React Hook Form tracks dirty state correctly even though the value is not used.
13+
// React Hook Form only tracks dirty fields when you explicitly subscribe to the form state.
14+
// Without this line, the keepDirty: true option in reset() will not work properly because
15+
// React Hook Form won't be tracking which fields are dirty. This is a consequence of
16+
// React Hook Form's performance optimization that only subscribes to form state
17+
// that is explicitly destructured/accessed in the component.
18+
const {
19+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
20+
formState: { isDirty },
21+
} = methods;
22+
return methods;
23+
}

src/components/dialogs/network-modifications/battery/modification/battery-modification-dialog.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77

8-
import { useForm } from 'react-hook-form';
98
import { useCallback, useEffect, useState } from 'react';
109
import { CustomFormProvider, EquipmentType, MODIFICATION_TYPES, useSnackMessage } from '@gridsuite/commons-ui';
1110
import { yupResolver } from '@hookform/resolvers/yup';
@@ -74,6 +73,7 @@ import BatteryModificationForm from './battery-modification-form';
7473
import { getSetPointsEmptyFormData, getSetPointsSchema } from '../../../set-points/set-points-utils';
7574
import { ModificationDialog } from '../../../commons/modificationDialog';
7675
import { EquipmentModificationDialogProps } from '../../../../graph/menus/network-modifications/network-modification-menu.type';
76+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
7777

7878
const emptyFormData = {
7979
[EQUIPMENT_NAME]: '',
@@ -126,8 +126,7 @@ export default function BatteryModificationDialog({
126126
const [selectedId, setSelectedId] = useState<string>(defaultIdValue ?? null);
127127
const [batteryToModify, setBatteryToModify] = useState<BatteryFormInfos | null>(null);
128128
const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE);
129-
130-
const formMethods = useForm<DeepNullable<BatteryModificationDialogSchemaForm>>({
129+
const formMethods = useFormWithDirtyTracking<DeepNullable<BatteryModificationDialogSchemaForm>>({
131130
defaultValues: emptyFormData,
132131
resolver: yupResolver<DeepNullable<BatteryModificationDialogSchemaForm>>(formSchema),
133132
});
@@ -248,7 +247,6 @@ export default function BatteryModificationDialog({
248247
setDataFetchStatus(FetchStatus.FAILED);
249248
if (editData?.equipmentId !== equipmentId) {
250249
setBatteryToModify(null);
251-
reset(emptyFormData);
252250
}
253251
});
254252
} else {

src/components/dialogs/network-modifications/generator/modification/generator-modification-dialog.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77

8-
import { useForm } from 'react-hook-form';
98
import { ModificationDialog } from '../../../commons/modificationDialog';
109
import { useCallback, useEffect, useState } from 'react';
1110
import { CustomFormProvider, EquipmentType, MODIFICATION_TYPES, useSnackMessage } from '@gridsuite/commons-ui';
@@ -95,6 +94,7 @@ import { DeepNullable } from '../../../../utils/ts-utils';
9594
import { GeneratorFormInfos, GeneratorModificationDialogSchemaForm } from '../generator-dialog.type';
9695
import { toModificationOperation } from '../../../../utils/utils';
9796
import { EquipmentModificationDialogProps } from '../../../../graph/menus/network-modifications/network-modification-menu.type';
97+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
9898

9999
const emptyFormData = {
100100
[EQUIPMENT_NAME]: '',
@@ -167,7 +167,7 @@ export default function GeneratorModificationDialog({
167167
const [generatorToModify, setGeneratorToModify] = useState<GeneratorFormInfos | null>();
168168
const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE);
169169

170-
const formMethods = useForm<DeepNullable<GeneratorModificationDialogSchemaForm>>({
170+
const formMethods = useFormWithDirtyTracking<DeepNullable<GeneratorModificationDialogSchemaForm>>({
171171
defaultValues: emptyFormData,
172172
resolver: yupResolver<DeepNullable<GeneratorModificationDialogSchemaForm>>(formSchema),
173173
});
@@ -307,7 +307,6 @@ export default function GeneratorModificationDialog({
307307
setDataFetchStatus(FetchStatus.FAILED);
308308
if (editData?.equipmentId !== equipmentId) {
309309
setGeneratorToModify(null);
310-
reset(emptyFormData);
311310
}
312311
});
313312
} else {

src/components/dialogs/network-modifications/hvdc-line/lcc/modification/lcc-modification-dialog.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
} from '../../../../../utils/field-constants';
2222
import yup from '../../../../../utils/yup-config';
2323
import { CustomFormProvider, ExtendedEquipmentType, MODIFICATION_TYPES, useSnackMessage } from '@gridsuite/commons-ui';
24-
import { useForm } from 'react-hook-form';
2524
import { yupResolver } from '@hookform/resolvers/yup';
2625
import { LccDialogTab, LccFormInfos, LccModificationSchemaForm } from '../common/lcc-type';
2726
import { useCallback, useEffect, useState } from 'react';
@@ -51,6 +50,7 @@ import { LccModificationForm } from './lcc-modification-form';
5150
import { toModificationOperation } from '../../../../../utils/utils';
5251
import { LccConverterStationModificationInfos, LccModificationInfos } from 'services/network-modification-types';
5352
import { DeepNullable } from '../../../../../utils/ts-utils';
53+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
5454

5555
const emptyFormData = {
5656
[EQUIPMENT_ID]: '',
@@ -93,7 +93,7 @@ export const LccModificationDialog = ({
9393
const currentNodeUuid = currentNode?.id;
9494
const { snackError } = useSnackMessage();
9595

96-
const formMethods = useForm<DeepNullable<LccModificationSchemaForm>>({
96+
const formMethods = useFormWithDirtyTracking<DeepNullable<LccModificationSchemaForm>>({
9797
defaultValues: emptyFormData,
9898
resolver: yupResolver<DeepNullable<LccModificationSchemaForm>>(formSchema),
9999
});
@@ -237,7 +237,6 @@ export const LccModificationDialog = ({
237237
setDataFetchStatus(FetchStatus.FAILED);
238238
if (editData?.equipmentId !== equipmentId) {
239239
setLccToModify(null);
240-
reset(emptyFormData);
241240
}
242241
});
243242
}

src/components/dialogs/network-modifications/hvdc-line/vsc/modification/vsc-modification-dialog.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
import { useCallback, useEffect, useState } from 'react';
9-
import { useForm } from 'react-hook-form';
109
import { ModificationDialog } from '../../../../commons/modificationDialog';
1110
import { EquipmentIdSelector } from '../../../../equipment-id/equipment-id-selector';
1211
import { EQUIPMENT_INFOS_TYPES } from 'components/utils/equipment-types';
@@ -70,6 +69,7 @@ import {
7069
} from '../../../common/properties/property-utils';
7170
import { isNodeBuilt } from '../../../../../graph/util/model-functions';
7271
import { ReactiveCapabilityCurvePoints } from '../../../../reactive-limits/reactive-limits.type';
72+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
7373

7474
const formSchema = yup
7575
.object()
@@ -112,7 +112,7 @@ const VscModificationDialog: React.FC<any> = ({
112112
const [equipmentId, setEquipmentId] = useState<string | null>(defaultIdValue ?? null);
113113
const [vscToModify, setVscToModify] = useState<VscModificationInfo | null>(null);
114114
const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE);
115-
const formMethods = useForm({
115+
const formMethods = useFormWithDirtyTracking({
116116
defaultValues: emptyFormData,
117117
resolver: yupResolver(formSchema),
118118
});
@@ -232,7 +232,6 @@ const VscModificationDialog: React.FC<any> = ({
232232
setDataFetchStatus(FetchStatus.FAILED);
233233
if (editData?.equipmentId !== equipmentId) {
234234
setVscToModify(null);
235-
reset(emptyFormData);
236235
}
237236
});
238237
}

src/components/dialogs/network-modifications/line/modification/line-modification-dialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import {
4949
SELECTED_LIMITS_GROUP_2,
5050
OPERATIONAL_LIMITS_GROUPS,
5151
} from 'components/utils/field-constants';
52-
import { FieldErrors, useForm } from 'react-hook-form';
52+
import { FieldErrors } from 'react-hook-form';
5353
import { sanitizeString } from 'components/dialogs/dialog-utils';
5454
import yup from 'components/utils/yup-config';
5555
import { ModificationDialog } from '../../../commons/modificationDialog';
@@ -104,6 +104,7 @@ import { useIntl } from 'react-intl';
104104
import { LimitsDialogFormInfos, LineModificationFormInfos } from './line-modification-type';
105105
import { LineModificationInfos } from '../../../../../services/network-modification-types';
106106
import { toModificationOperation } from '../../../../utils/utils';
107+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
107108

108109
export interface LineModificationDialogProps {
109110
// contains data when we try to edit an existing hypothesis from the current node's list
@@ -177,7 +178,7 @@ const LineModificationDialog = ({
177178
.concat(modificationPropertiesSchema)
178179
.required();
179180

180-
const formMethods = useForm({
181+
const formMethods = useFormWithDirtyTracking({
181182
defaultValues: emptyFormData,
182183
resolver: yupResolver(formSchema),
183184
});
@@ -327,7 +328,6 @@ const LineModificationDialog = ({
327328
setDataFetchStatus(FetchStatus.FAILED);
328329
if (editData?.equipmentId !== equipmentId) {
329330
setLineToModify(null);
330-
reset(emptyFormData);
331331
}
332332
});
333333
} else {

src/components/dialogs/network-modifications/load/modification/load-modification-dialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
VOLTAGE_LEVEL,
3131
} from 'components/utils/field-constants';
3232
import { useCallback, useEffect, useState } from 'react';
33-
import { FieldErrors, useForm } from 'react-hook-form';
33+
import { FieldErrors } from 'react-hook-form';
3434
import { sanitizeString } from '../../../dialog-utils';
3535
import yup from 'components/utils/yup-config';
3636
import { ModificationDialog } from '../../../commons/modificationDialog';
@@ -66,6 +66,7 @@ import { LoadFormInfos } from '../common/load.type';
6666
import { DeepNullable } from 'components/utils/ts-utils';
6767
import { getSetPointsEmptyFormData, getSetPointsSchema } from 'components/dialogs/set-points/set-points-utils';
6868
import useVoltageLevelsListInfos from '../../../../../hooks/use-voltage-levels-list-infos';
69+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
6970

7071
const emptyFormData = {
7172
[EQUIPMENT_NAME]: '',
@@ -111,7 +112,7 @@ export default function LoadModificationDialog({
111112
const [dataFetchStatus, setDataFetchStatus] = useState<string>(FetchStatus.IDLE);
112113
const voltageLevelOptions = useVoltageLevelsListInfos(studyUuid, currentNodeUuid, currentRootNetworkUuid);
113114

114-
const formMethods = useForm<DeepNullable<LoadModificationSchemaForm>>({
115+
const formMethods = useFormWithDirtyTracking<DeepNullable<LoadModificationSchemaForm>>({
115116
defaultValues: emptyFormData,
116117
resolver: yupResolver<DeepNullable<LoadModificationSchemaForm>>(formSchema),
117118
});
@@ -183,7 +184,6 @@ export default function LoadModificationDialog({
183184
setDataFetchStatus(FetchStatus.FAILED);
184185
if (editData?.equipmentId !== equipmentId) {
185186
setLoadToModify(null);
186-
reset(emptyFormData);
187187
}
188188
});
189189
}

src/components/dialogs/network-modifications/shunt-compensator/modification/shunt-compensator-modification-dialog.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import {
3030
getCharacteristicsFormData,
3131
getCharacteristicsFormValidationSchema,
3232
} from '../characteristics-pane/characteristics-form-utils';
33-
import { useForm } from 'react-hook-form';
3433
import yup from '../../../../utils/yup-config';
3534
import { yupResolver } from '@hookform/resolvers/yup';
3635
import { useCallback, useEffect, useState } from 'react';
@@ -57,6 +56,7 @@ import {
5756
getConnectivityWithPositionValidationSchema,
5857
} from '../../../connectivity/connectivity-form-utils';
5958
import { isNodeBuilt } from '../../../../graph/util/model-functions.ts';
59+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
6060

6161
const emptyFormData = {
6262
[EQUIPMENT_NAME]: '',
@@ -95,7 +95,7 @@ const ShuntCompensatorModificationDialog = ({
9595
const [idExists, setIdExists] = useState(null);
9696
const [loading, setLoading] = useState(false);
9797

98-
const formMethods = useForm({
98+
const formMethods = useFormWithDirtyTracking({
9999
defaultValues: emptyFormData,
100100
resolver: yupResolver(formSchema),
101101
});
@@ -200,7 +200,6 @@ const ShuntCompensatorModificationDialog = ({
200200
setLoading(false);
201201
if (editData?.equipmentId !== equipmentId) {
202202
setShuntCompensatorInfos(null);
203-
reset(emptyFormData);
204203
}
205204
});
206205
} else {

src/components/dialogs/network-modifications/substation/modification/substation-modification-dialog.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77

8-
import { useForm } from 'react-hook-form';
98
import { ModificationDialog } from '../../../commons/modificationDialog';
109
import { useCallback, useEffect, useState } from 'react';
1110
import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui';
@@ -29,6 +28,7 @@ import {
2928
toModificationProperties,
3029
} from '../../common/properties/property-utils';
3130
import { isNodeBuilt } from '../../../../graph/util/model-functions.ts';
31+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
3232

3333
const emptyFormData = {
3434
[EQUIPMENT_NAME]: '',
@@ -70,7 +70,7 @@ const SubstationModificationDialog = ({
7070
const [substationToModify, setSubstationToModify] = useState(null);
7171
const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE);
7272

73-
const formMethods = useForm({
73+
const formMethods = useFormWithDirtyTracking({
7474
defaultValues: emptyFormData,
7575
resolver: yupResolver(formSchema),
7676
});
@@ -126,7 +126,6 @@ const SubstationModificationDialog = ({
126126
setDataFetchStatus(FetchStatus.FAILED);
127127
if (editData?.equipmentId !== equipmentId) {
128128
setSubstationToModify(null);
129-
reset(emptyFormData);
130129
}
131130
});
132131
}

src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ import {
7171
} from 'components/utils/field-constants';
7272
import PropTypes from 'prop-types';
7373
import { useCallback, useEffect, useState } from 'react';
74-
import { useForm } from 'react-hook-form';
7574
import { sanitizeString } from '../../../dialog-utils';
7675
import {
7776
FORM_LOADING_DELAY,
@@ -151,6 +150,7 @@ import {
151150
} from './state-estimation-form-utils';
152151
import { LimitsPane } from '../../../limits/limits-pane';
153152
import { useIntl } from 'react-intl';
153+
import { useFormWithDirtyTracking } from 'components/dialogs/commons/use-form-with-dirty-tracking';
154154

155155
const emptyFormData = {
156156
[EQUIPMENT_NAME]: '',
@@ -207,7 +207,7 @@ const TwoWindingsTransformerModificationDialog = ({
207207
const [twtToModify, setTwtToModify] = useState(null);
208208
const intl = useIntl();
209209

210-
const formMethods = useForm({
210+
const formMethods = useFormWithDirtyTracking({
211211
defaultValues: emptyFormData,
212212
resolver: yupResolver(formSchema),
213213
});
@@ -711,7 +711,6 @@ const TwoWindingsTransformerModificationDialog = ({
711711
setDataFetchStatus(FetchStatus.FAILED);
712712
if (editData?.equipmentId !== equipmentId) {
713713
setTwtToModify(null);
714-
reset(emptyFormData);
715714
}
716715
});
717716
} else {

0 commit comments

Comments
 (0)