Skip to content

Commit

Permalink
Changed FormField to handle required with an initial value to fix is…
Browse files Browse the repository at this point in the history
…sue grommet#2948 (grommet#2951)

* Changed FormField to handle required with an initial value to fix issue grommet#2948

* Update FormField README

* Add a test for non requiring default value

* Fixed issues with attaching refs to FormField
  • Loading branch information
ericsoderberghp authored and ShimiSun committed May 3, 2019
1 parent 70c6fcc commit 79b4850
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 148 deletions.
19 changes: 19 additions & 0 deletions src/js/components/Form/__tests__/Form-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,23 @@ describe('Form', () => {
fireEvent.click(getByText('Reset'));
expect(queryByText('Input has changed')).toBeNull();
});

test('initial values', () => {
const onSubmit = jest.fn();
const { getByText, queryByText } = render(
<Grommet>
<Form onSubmit={onSubmit}>
<FormField
name="test"
required
placeholder="test input"
value="Initial value"
/>
<Button type="submit" primary label="Submit" />
</Form>
</Grommet>,
);
fireEvent.click(getByText('Submit'));
expect(queryByText('required')).toBeNull();
});
});
18 changes: 1 addition & 17 deletions src/js/components/Form/form.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,13 @@ import {
Grommet,
Form,
FormField,
RadioButton,
RadioButtonGroup,
RangeInput,
Select,
TextArea,
} from 'grommet';
import { grommet } from 'grommet/themes';

const RadioButtonGroup = ({ name, onChange, options, value }) => (
<Box gap="small">
{options.map(option => (
<Box key={option}>
<RadioButton
name={name}
value={option}
label={option}
checked={value === option}
onChange={() => onChange({ value: option })}
/>
</Box>
))}
</Box>
);

const Example = () => (
<Grommet full theme={grommet}>
<Box fill align="center" justify="center">
Expand Down
278 changes: 151 additions & 127 deletions src/js/components/FormField/FormField.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,36 @@ const FormFieldBox = styled(Box)`
${props => props.theme.formField.extend}
`;

class FormField extends Component {
class FormFieldContent extends Component {
componentDidMount() {
const { checked, context, name, value } = this.props;
if (
context &&
context.value[name] === undefined &&
(value !== undefined || checked !== undefined)
) {
context.update(name, value !== undefined ? value : checked);
}
}

renderChildren = (value, update) => {
const { name, component, required, onChange, ...rest } = this.props;
const {
name,
checked,
component,
required,
value: valueProp,
onChange,
...rest
} = this.props;

delete rest.className;
const Input = component || TextInput;
if (Input === CheckBox) {
return (
<Input
name={name}
checked={value[name] || false}
checked={value[name] !== undefined ? value[name] : checked || false}
onChange={event => {
update(name, event.target.checked);
if (onChange) onChange(event);
Expand All @@ -52,9 +72,9 @@ class FormField extends Component {
return (
<Input
name={name}
value={value[name] || ''}
value={value[name] !== undefined ? value[name] : valueProp || ''}
onChange={event => {
update(name, event.value || event.target.value);
update(name, event.value || event.target.value || '');
if (onChange) onChange(event);
}}
plain
Expand All @@ -69,6 +89,7 @@ class FormField extends Component {
children,
className,
component,
context,
error,
focus,
help,
Expand All @@ -86,133 +107,136 @@ class FormField extends Component {
const { formField } = theme;
const { border } = formField;

return (
<FormContext.Consumer>
{context => {
let normalizedError = error;
let contents = children;

if (context) {
const { addValidation, errors, value, update, messages } = context;
addValidation(name, validateField(required, validate, messages));
normalizedError = error || errors[name];
contents = children || this.renderChildren(value, update);
}
let normalizedError = error;
let contents = children;

if (pad) {
contents = <Box {...formField.content}>{contents}</Box>;
}
if (context) {
const { addValidation, errors, value, update, messages } = context;
addValidation(name, validateField(required, validate, messages));
normalizedError = error || errors[name];
contents = children || this.renderChildren(value, update);
}

let borderColor;
if (focus && !normalizedError) {
borderColor = 'focus';
} else if (normalizedError) {
borderColor = (border && border.error.color) || 'status-critical';
} else {
borderColor = (border && border.color) || 'border';
}
let abut;
let outerStyle = style;

if (border) {
const normalizedChildren = children
? Children.map(children, child => {
if (child) {
return cloneElement(child, {
plain: true,
focusIndicator: false,
onBlur,
onFocus,
});
}
return child;
})
: contents;
contents = (
<Box
ref={ref => {
this.childContainerRef = ref;
}}
border={
border.position === 'inner'
? {
...border,
side: border.side || 'bottom',
color: borderColor,
}
: undefined
}
>
{normalizedChildren}
</Box>
);

abut =
border.position === 'outer' &&
(border.side === 'all' ||
border.side === 'horizontal' ||
!border.side);
if (abut) {
// marginBottom is set to overlap adjacent fields
let marginBottom = '-1px';
if (border.size) {
marginBottom = `-${parseMetricToNum(
theme.global.borderSize[border.size],
)}px`;
}
outerStyle = {
position: focus ? 'relative' : undefined,
marginBottom,
zIndex: focus ? 10 : undefined,
...style,
};
if (pad) {
contents = <Box {...formField.content}>{contents}</Box>;
}

let borderColor;
if (focus && !normalizedError) {
borderColor = 'focus';
} else if (normalizedError) {
borderColor = (border && border.error.color) || 'status-critical';
} else {
borderColor = (border && border.color) || 'border';
}
let abut;
let outerStyle = style;

if (border) {
const normalizedChildren = children
? Children.map(children, child => {
if (child) {
return cloneElement(child, {
plain: true,
focusIndicator: false,
onBlur,
onFocus,
});
}
return child;
})
: contents;
contents = (
<Box
ref={ref => {
this.childContainerRef = ref;
}}
border={
border.position === 'inner'
? {
...border,
side: border.side || 'bottom',
color: borderColor,
}
: undefined
}
>
{normalizedChildren}
</Box>
);

return (
<FormFieldBox
className={className}
border={
border && border.position === 'outer'
? { ...border, color: borderColor }
: undefined
}
margin={abut ? undefined : { ...formField.margin }}
style={outerStyle}
>
{(label && component !== CheckBox) || help ? (
<>
{label && component !== CheckBox && (
<Text as="label" htmlFor={htmlFor} {...formField.label}>
{label}
</Text>
)}
{help && (
<Text
{...formField.help}
color={
formField.help.color[theme.dark ? 'dark' : 'light']
}
>
{help}
</Text>
)}
</>
) : (
undefined
)}
{contents}
{normalizedError && (
<Text
{...formField.error}
color={formField.error.color[theme.dark ? 'dark' : 'light']}
>
{normalizedError}
</Text>
)}
</FormFieldBox>
);
}}
abut =
border.position === 'outer' &&
(border.side === 'all' || border.side === 'horizontal' || !border.side);
if (abut) {
// marginBottom is set to overlap adjacent fields
let marginBottom = '-1px';
if (border.size) {
marginBottom = `-${parseMetricToNum(
theme.global.borderSize[border.size],
)}px`;
}
outerStyle = {
position: focus ? 'relative' : undefined,
marginBottom,
zIndex: focus ? 10 : undefined,
...style,
};
}
}

return (
<FormFieldBox
className={className}
border={
border && border.position === 'outer'
? { ...border, color: borderColor }
: undefined
}
margin={abut ? undefined : { ...formField.margin }}
style={outerStyle}
>
{(label && component !== CheckBox) || help ? (
<>
{label && component !== CheckBox && (
<Text as="label" htmlFor={htmlFor} {...formField.label}>
{label}
</Text>
)}
{help && (
<Text
{...formField.help}
color={formField.help.color[theme.dark ? 'dark' : 'light']}
>
{help}
</Text>
)}
</>
) : (
undefined
)}
{contents}
{normalizedError && (
<Text
{...formField.error}
color={formField.error.color[theme.dark ? 'dark' : 'light']}
>
{normalizedError}
</Text>
)}
</FormFieldBox>
);
}
}

// Can't be a functional component because styled-components withTheme() needs
// to attach a ref.
/* eslint-disable-next-line react/no-multi-comp, react/prefer-stateless-function */
class FormField extends Component {
render() {
return (
<FormContext.Consumer>
{context => <FormFieldContent context={context} {...this.props} />}
</FormContext.Consumer>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/js/components/FormField/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ boolean

**validate**

Validation rule. Provide a regular expression or a function. If a
Validation rule when used within a grommet Form. Provide a regular
expression or a function. If a
function is provided, it will be called with two arguments, the value
for this field and the entire value object. This permits validation to
encompass multiple fields. The function should return a string message
Expand Down
3 changes: 2 additions & 1 deletion src/js/components/FormField/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export const doc = FormField => {
}),
PropTypes.func,
]).description(
`Validation rule. Provide a regular expression or a function. If a
`Validation rule when used within a grommet Form. Provide a regular
expression or a function. If a
function is provided, it will be called with two arguments, the value
for this field and the entire value object. This permits validation to
encompass multiple fields. The function should return a string message
Expand Down
Loading

0 comments on commit 79b4850

Please sign in to comment.