Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/reset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const Demo = () => {
<Item name={['path1', 'path2']} rules={[{ required: true }]}>
<Input placeholder="nest" />
</Item>
<Item name={['path1', 'path3']} initialValue="bamboo">
<Input placeholder="nest" />
</Item>
<button
type="button"
onClick={() => {
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"now-build": "npm run build"
},
"peerDependencies": {
"react": "*"
"react": "^16.9.0"
},
"devDependencies": {
"@types/enzyme": "^3.10.5",
Expand All @@ -53,10 +53,10 @@
"enzyme-to-json": "^3.1.4",
"father": "^2.13.6",
"np": "^5.0.3",
"react": "^v16.9.0-alpha.0",
"react": "^16.14.0",
"react-dnd": "^8.0.3",
"react-dnd-html5-backend": "^8.0.3",
"react-dom": "^v16.9.0-alpha.0",
"react-dom": "^16.14.0",
"react-redux": "^4.4.10",
"react-router": "^3.0.0",
"redux": "^3.7.2",
Expand Down
57 changes: 36 additions & 21 deletions src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,14 @@ export interface InternalFieldProps<Values = any> {

/** @private Passed by Form.List props. Do not use since it will break by path check. */
isListField?: boolean;

/** @private Pass context as prop instead of context api
* since class component can not get context in constructor */
fieldContext: InternalFormInstance;
}

export interface FieldProps<Values = any> extends Omit<InternalFieldProps<Values>, 'name'> {
export interface FieldProps<Values = any>
extends Omit<InternalFieldProps<Values>, 'name' | 'fieldContext'> {
name?: NamePath;
}

Expand All @@ -87,24 +92,21 @@ export interface FieldState {
}

// We use Class instead of Hooks here since it will cost much code by using Hooks.
class Field extends React.Component<InternalFieldProps, FieldState, InternalFormInstance>
implements FieldEntity {
class Field extends React.Component<InternalFieldProps, FieldState> implements FieldEntity {
public static contextType = FieldContext;

public static defaultProps = {
trigger: 'onChange',
valuePropName: 'value',
};

context: InternalFormInstance;

public state = {
resetCount: 0,
};

private cancelRegisterFunc: (isListField?: boolean, preserve?: boolean) => void | null = null;

private destroy = false;
private mounted = false;

/**
* Follow state should not management in State since it will async update by React.
Expand All @@ -122,11 +124,21 @@ class Field extends React.Component<InternalFieldProps, FieldState, InternalForm
private errors: string[] = [];

// ============================== Subscriptions ==============================
constructor(props: InternalFieldProps) {
super(props);

// Register on init
if (props.fieldContext) {
const { getInternalHooks }: InternalFormInstance = props.fieldContext;
const { registerField } = getInternalHooks(HOOK_MARK);
this.cancelRegisterFunc = registerField(this);
}
}

public componentDidMount() {
const { shouldUpdate } = this.props;
const { getInternalHooks }: InternalFormInstance = this.context;
const { registerField } = getInternalHooks(HOOK_MARK);
this.cancelRegisterFunc = registerField(this);

this.mounted = true;

// One more render for component in case fields not ready
if (shouldUpdate === true) {
Expand All @@ -136,7 +148,7 @@ class Field extends React.Component<InternalFieldProps, FieldState, InternalForm

public componentWillUnmount() {
this.cancelRegister();
this.destroy = true;
this.mounted = false;
}

public cancelRegister = () => {
Expand All @@ -150,32 +162,32 @@ class Field extends React.Component<InternalFieldProps, FieldState, InternalForm

// ================================== Utils ==================================
public getNamePath = (): InternalNamePath => {
const { name } = this.props;
const { prefixName = [] }: InternalFormInstance = this.context;
const { name, fieldContext } = this.props;
const { prefixName = [] }: InternalFormInstance = fieldContext;

return name !== undefined ? [...prefixName, ...name] : [];
};

public getRules = (): RuleObject[] => {
const { rules = [] } = this.props;
const { rules = [], fieldContext } = this.props;

return rules.map(
(rule: Rule): RuleObject => {
if (typeof rule === 'function') {
return rule(this.context);
return rule(fieldContext);
}
return rule;
},
);
};

public reRender() {
if (this.destroy) return;
if (!this.mounted) return;
this.forceUpdate();
}

public refresh = () => {
if (this.destroy) return;
if (!this.mounted) return;

/**
* Clean up current node.
Expand Down Expand Up @@ -373,7 +385,7 @@ class Field extends React.Component<InternalFieldProps, FieldState, InternalForm
const meta = this.getMeta();

return {
...this.getOnlyChild(children(this.getControlled(), meta, this.context)),
...this.getOnlyChild(children(this.getControlled(), meta, this.props.fieldContext)),
isFunction: true,
};
}
Expand All @@ -389,7 +401,7 @@ class Field extends React.Component<InternalFieldProps, FieldState, InternalForm

// ============================== Field Control ==============================
public getValue = (store?: Store) => {
const { getFieldsValue }: FormInstance = this.context;
const { getFieldsValue }: FormInstance = this.props.fieldContext;
const namePath = this.getNamePath();
return getValue(store || getFieldsValue(true), namePath);
};
Expand All @@ -402,13 +414,14 @@ class Field extends React.Component<InternalFieldProps, FieldState, InternalForm
normalize,
valuePropName,
getValueProps,
fieldContext,
} = this.props;

const mergedValidateTrigger =
validateTrigger !== undefined ? validateTrigger : this.context.validateTrigger;
validateTrigger !== undefined ? validateTrigger : fieldContext.validateTrigger;

const namePath = this.getNamePath();
const { getInternalHooks, getFieldsValue }: InternalFormInstance = this.context;
const { getInternalHooks, getFieldsValue }: InternalFormInstance = fieldContext;
const { dispatch } = getInternalHooks(HOOK_MARK);
const value = this.getValue();
const mergedGetValueProps = getValueProps || ((val: StoreValue) => ({ [valuePropName]: val }));
Expand Down Expand Up @@ -502,6 +515,8 @@ class Field extends React.Component<InternalFieldProps, FieldState, InternalForm
}

function WrapperField<Values = any>({ name, ...restProps }: FieldProps<Values>) {
const fieldContext = React.useContext(FieldContext);

const namePath = name !== undefined ? getNamePath(name) : undefined;

let key: string = 'keep';
Expand All @@ -516,7 +531,7 @@ function WrapperField<Values = any>({ name, ...restProps }: FieldProps<Values>)
);
}

return <Field key={key} name={namePath} {...restProps} />;
return <Field key={key} name={namePath} {...restProps} fieldContext={fieldContext} />;
}

export default WrapperField;
29 changes: 29 additions & 0 deletions tests/field.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { mount } from 'enzyme';
import Form, { Field } from '../src';

describe('Form.Field', () => {
it('field remount should trigger constructor again', () => {
const Demo = ({ visible }: { visible: boolean }) => {
const [form] = Form.useForm();

const fieldNode = <Field name="light" initialValue="bamboo" />;

return <Form form={form}>{visible ? fieldNode : null}</Form>;
};

// First mount
const wrapper = mount(<Demo visible />);
const instance = wrapper.find('Field').instance() as any;
expect(instance.cancelRegisterFunc).toBeTruthy();

// Hide
wrapper.setProps({ visible: false });
expect(instance.cancelRegisterFunc).toBeFalsy();

// Mount again
wrapper.setProps({ visible: true });
expect(instance.cancelRegisterFunc).toBeFalsy();
expect((wrapper.find('Field').instance() as any).cancelRegisterFunc).toBeTruthy();
});
});
30 changes: 30 additions & 0 deletions tests/initialValue.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,35 @@ describe('Form.InitialValues', () => {
wrapper.find('button').simulate('click');
expect(wrapper.find('input').props().value).toEqual('bamboo');
});

it('not initialValue when not mount', () => {
let formInstance;

const Test = () => {
const [form] = Form.useForm();
formInstance = form;

const fieldNode = <Field name="bamboo" initialValue="light" />;

expect(fieldNode).toBeTruthy();

return (
<Form form={form}>
<Field name="light" initialValue="bamboo">
{control => {
expect(control.value).toEqual('bamboo');
return null;
}}
</Field>
</Form>
);
};

const wrapper = mount(<Test />);

expect(formInstance.getFieldsValue()).toEqual({ light: 'bamboo' });

wrapper.unmount();
});
});
});