Skip to content

Commit 8183f63

Browse files
authored
fix: Remount form should keep origin store value (#397)
* test: test driven * test: fix test * test: fix test * test: fix test * test: fix test * fix: back of test one * fix: preserve logic * chore: fix compile
1 parent 4e7448b commit 8183f63

File tree

6 files changed

+102
-14
lines changed

6 files changed

+102
-14
lines changed

src/FieldContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const Context = React.createContext<InternalFormInstance>({
3434
registerField: warningFunc,
3535
useSubscribe: warningFunc,
3636
setInitialValues: warningFunc,
37+
destroyForm: warningFunc,
3738
setCallbacks: warningFunc,
3839
getFields: warningFunc,
3940
setValidateMessages: warningFunc,

src/Form.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const Form: React.ForwardRefRenderFunction<FormInstance, FormProps> = (
6464
setCallbacks,
6565
setValidateMessages,
6666
setPreserve,
67+
destroyForm,
6768
} = (formInstance as InternalFormInstance).getInternalHooks(HOOK_MARK);
6869

6970
// Pass ref with form instance
@@ -109,6 +110,12 @@ const Form: React.ForwardRefRenderFunction<FormInstance, FormProps> = (
109110
mountRef.current = true;
110111
}
111112

113+
React.useEffect(
114+
() => destroyForm,
115+
// eslint-disable-next-line react-hooks/exhaustive-deps
116+
[],
117+
);
118+
112119
// Prepare children by `children` type
113120
let childrenNode = children;
114121
const childrenRenderProps = typeof children === 'function';

src/interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export interface InternalHooks {
199199
registerField: (entity: FieldEntity) => () => void;
200200
useSubscribe: (subscribable: boolean) => void;
201201
setInitialValues: (values: Store, init: boolean) => void;
202+
destroyForm: () => void;
202203
setCallbacks: (callbacks: Callbacks) => void;
203204
getFields: (namePathList?: InternalNamePath[]) => FieldData[];
204205
setValidateMessages: (validateMessages: ValidateMessages) => void;

src/useForm.ts

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export class FormStore {
108108
registerField: this.registerField,
109109
useSubscribe: this.useSubscribe,
110110
setInitialValues: this.setInitialValues,
111+
destroyForm: this.destroyForm,
111112
setCallbacks: this.setCallbacks,
112113
setValidateMessages: this.setValidateMessages,
113114
getFields: this.getFields,
@@ -124,18 +125,47 @@ export class FormStore {
124125
this.subscribable = subscribable;
125126
};
126127

128+
/**
129+
* Record prev Form unmount fieldEntities which config preserve false.
130+
* This need to be refill with initialValues instead of store value.
131+
*/
132+
private prevWithoutPreserves: NameMap<boolean> | null = null;
133+
127134
/**
128135
* First time `setInitialValues` should update store with initial value
129136
*/
130137
private setInitialValues = (initialValues: Store, init: boolean) => {
131138
this.initialValues = initialValues || {};
132139
if (init) {
133-
this.store = setValues({}, this.store, initialValues);
140+
let nextStore = setValues({}, initialValues, this.store);
141+
142+
// We will take consider prev form unmount fields.
143+
// When the field is not `preserve`, we need fill this with initialValues instead of store.
144+
this.prevWithoutPreserves?.map(({ key: namePath }) => {
145+
nextStore = setValue(nextStore, namePath, getValue(initialValues, namePath));
146+
});
147+
this.prevWithoutPreserves = null;
148+
149+
this.updateStore(nextStore);
134150
}
135151
};
136152

153+
private destroyForm = () => {
154+
const prevWithoutPreserves = new NameMap<boolean>();
155+
this.getFieldEntities(true).forEach(entity => {
156+
if (!entity.isPreserve()) {
157+
prevWithoutPreserves.set(entity.getNamePath(), true);
158+
}
159+
});
160+
161+
this.prevWithoutPreserves = prevWithoutPreserves;
162+
};
163+
137164
private getInitialValue = (namePath: InternalNamePath) => {
138-
return cloneDeep(getValue(this.initialValues, namePath));
165+
const initValue = getValue(this.initialValues, namePath);
166+
167+
// Not cloneDeep when without `namePath`
168+
return namePath.length ? cloneDeep(initValue) : initValue;
139169
};
140170

141171
private setCallbacks = (callbacks: Callbacks) => {
@@ -168,6 +198,11 @@ export class FormStore {
168198
}
169199
};
170200

201+
// ============================ Store =============================
202+
private updateStore = (nextStore: Store) => {
203+
this.store = nextStore;
204+
};
205+
171206
// ============================ Fields ============================
172207
/**
173208
* Get registered field entities.
@@ -428,7 +463,7 @@ export class FormStore {
428463
const originValue = this.getFieldValue(namePath);
429464
// Set `initialValue`
430465
if (!info.skipExist || originValue === undefined) {
431-
this.store = setValue(this.store, namePath, [...records][0].value);
466+
this.updateStore(setValue(this.store, namePath, [...records][0].value));
432467
}
433468
}
434469
}
@@ -460,7 +495,7 @@ export class FormStore {
460495

461496
const prevStore = this.store;
462497
if (!nameList) {
463-
this.store = setValues({}, this.initialValues);
498+
this.updateStore(setValues({}, this.initialValues));
464499
this.resetWithFieldInitialValue();
465500
this.notifyObservers(prevStore, null, { type: 'reset' });
466501
return;
@@ -470,7 +505,7 @@ export class FormStore {
470505
const namePathList: InternalNamePath[] = nameList.map(getNamePath);
471506
namePathList.forEach(namePath => {
472507
const initialValue = this.getInitialValue(namePath);
473-
this.store = setValue(this.store, namePath, initialValue);
508+
this.updateStore(setValue(this.store, namePath, initialValue));
474509
});
475510
this.resetWithFieldInitialValue({ namePathList });
476511
this.notifyObservers(prevStore, namePathList, { type: 'reset' });
@@ -487,7 +522,7 @@ export class FormStore {
487522

488523
// Value
489524
if ('value' in data) {
490-
this.store = setValue(this.store, namePath, data.value);
525+
this.updateStore(setValue(this.store, namePath, data.value));
491526
}
492527

493528
this.notifyObservers(prevStore, [namePath], {
@@ -531,7 +566,7 @@ export class FormStore {
531566
const prevValue = getValue(this.store, namePath);
532567

533568
if (prevValue === undefined) {
534-
this.store = setValue(this.store, namePath, initialValue);
569+
this.updateStore(setValue(this.store, namePath, initialValue));
535570
}
536571
}
537572
};
@@ -570,7 +605,7 @@ export class FormStore {
570605
)
571606
) {
572607
const prevStore = this.store;
573-
this.store = setValue(prevStore, namePath, defaultValue, true);
608+
this.updateStore(setValue(prevStore, namePath, defaultValue, true));
574609

575610
// Notify that field is unmount
576611
this.notifyObservers(prevStore, [namePath], { type: 'remove' });
@@ -638,7 +673,7 @@ export class FormStore {
638673
private updateValue = (name: NamePath, value: StoreValue) => {
639674
const namePath = getNamePath(name);
640675
const prevStore = this.store;
641-
this.store = setValue(this.store, namePath, value);
676+
this.updateStore(setValue(this.store, namePath, value));
642677

643678
this.notifyObservers(prevStore, [namePath], {
644679
type: 'valueUpdate',
@@ -666,7 +701,7 @@ export class FormStore {
666701
const prevStore = this.store;
667702

668703
if (store) {
669-
this.store = setValues(this.store, store);
704+
this.updateStore(setValues(this.store, store));
670705
}
671706

672707
this.notifyObservers(prevStore, null, {

tests/index.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,4 +803,39 @@ describe('Form.Basic', () => {
803803
const wrapper = mount(<Demo />);
804804
expect(wrapper.find('.select-div').text()).toBe('K1,K2');
805805
});
806+
807+
// https://github.com/ant-design/ant-design/issues/34768
808+
it('remount should not clear current value', () => {
809+
let refForm;
810+
811+
const Demo = ({ remount }) => {
812+
const [form] = Form.useForm();
813+
refForm = form;
814+
815+
let node = (
816+
<Form form={form} initialValues={{ name: 'little' }}>
817+
<Field name="name">
818+
<Input />
819+
</Field>
820+
</Form>
821+
);
822+
823+
if (remount) {
824+
node = <div>{node}</div>;
825+
}
826+
827+
return node;
828+
};
829+
830+
const wrapper = mount(<Demo />);
831+
refForm.setFieldsValue({ name: 'bamboo' });
832+
wrapper.update();
833+
834+
expect(wrapper.find('input').prop('value')).toEqual('bamboo');
835+
836+
wrapper.setProps({ remount: true });
837+
wrapper.update();
838+
839+
expect(wrapper.find('input').prop('value')).toEqual('bamboo');
840+
});
806841
});

tests/initialValue.test.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,20 @@ describe('Form.InitialValues', () => {
103103
expect(getField(wrapper, 'username').find('input').props().value).toEqual('Light');
104104
});
105105

106-
it(`initialValues shouldn't be modified if preserve is false`, () => {
106+
it("initialValues shouldn't be modified if preserve is false", () => {
107107
const formValue = {
108108
test: 'test',
109109
users: [{ first: 'aaa', last: 'bbb' }],
110110
};
111111

112+
let refForm;
113+
112114
const Demo = () => {
113115
const [form] = Form.useForm();
114116
const [show, setShow] = useState(false);
115117

118+
refForm = form;
119+
116120
return (
117121
<>
118122
<button onClick={() => setShow(prev => !prev)}>switch show</button>
@@ -129,7 +133,7 @@ describe('Form.InitialValues', () => {
129133
{fields => (
130134
<>
131135
{fields.map(({ key, name, ...restField }) => (
132-
<>
136+
<React.Fragment key={key}>
133137
<Field
134138
{...restField}
135139
name={[name, 'first']}
@@ -144,7 +148,7 @@ describe('Form.InitialValues', () => {
144148
>
145149
<Input placeholder="Last Name" />
146150
</Field>
147-
</>
151+
</React.Fragment>
148152
))}
149153
</>
150154
)}
@@ -158,10 +162,15 @@ describe('Form.InitialValues', () => {
158162
const wrapper = mount(<Demo />);
159163
wrapper.find('button').simulate('click');
160164
expect(formValue.users[0].last).toEqual('bbb');
165+
161166
wrapper.find('button').simulate('click');
162167
expect(formValue.users[0].last).toEqual('bbb');
168+
console.log('Form Value:', refForm.getFieldsValue(true));
169+
163170
wrapper.find('button').simulate('click');
164-
expect(wrapper.find('.first-name-input').first().find('input').instance().value).toEqual('aaa');
171+
wrapper.update();
172+
173+
expect(wrapper.find('.first-name-input').first().find('input').prop('value')).toEqual('aaa');
165174
});
166175

167176
describe('Field with initialValue', () => {

0 commit comments

Comments
 (0)