Skip to content

Commit

Permalink
Fix form validation nested objects treatment
Browse files Browse the repository at this point in the history
  • Loading branch information
moroshko committed Feb 21, 2020
1 parent 46e8133 commit 0f1cd88
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/components/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Checkbox.propTypes = {
disabled: PropTypes.bool,
optional: PropTypes.bool,
validate: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
validateData: PropTypes.object,
validateData: PropTypes.any,
children: PropTypes.node.isRequired,
testId: PropTypes.string,
__internal__keyboardFocus: PropTypes.bool
Expand Down
5 changes: 4 additions & 1 deletion src/components/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ function DatePicker(props) {
<Grid.Item colSpan={0}>
<InternalInput
name={`${name}.day`}
parentName={name}
color={color}
type="number"
placeholder="DD"
Expand All @@ -166,6 +167,7 @@ function DatePicker(props) {
<Grid.Item colSpan={1}>
<InternalInput
name={`${name}.month`}
parentName={name}
color={color}
type="number"
placeholder="MM"
Expand All @@ -179,6 +181,7 @@ function DatePicker(props) {
<Grid.Item colSpan="2-3">
<InternalInput
name={`${name}.year`}
parentName={name}
color={color}
type="number"
placeholder="YYYY"
Expand All @@ -203,7 +206,7 @@ DatePicker.propTypes = {
disabled: PropTypes.bool,
optional: PropTypes.bool,
validate: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
validateData: PropTypes.object,
validateData: PropTypes.any,
testId: PropTypes.string
};

Expand Down
56 changes: 24 additions & 32 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import React, {
} from "react";
import PropTypes from "prop-types";
import { FormProvider } from "../hooks/internal/useForm";
import { setPath } from "../utils/objectPath";
import { getPath, setPath, deletePath } from "../utils/objectPath";

const DEFAULT_PROPS = {
fullWidth: true,
debug: false
};

function getParentFieldName(name) {
return name.split(".")[0];
function getParentFieldName(target) {
return target.dataset.parentName ?? target.name;
}

Form.DEFAULT_PROPS = DEFAULT_PROPS;
Expand All @@ -41,23 +41,21 @@ function Form(_props) {
const lastFocusedFieldName = useRef(null);
const lastMouseDownInputElement = useRef(null);
const onFocus = event => {
const { name } = event.target;
const parentName = getParentFieldName(name);
const { target } = event;
const parentName = getParentFieldName(target);

lastFocusedFieldName.current = parentName;

setState(state =>
setPath(
state,
"shouldValidateOnChange",
Array.isArray(state.errors[parentName])
)
);
setState(state => {
const errors = getPath(state.errors, parentName);
const hasErrors = Array.isArray(errors) && errors.length > 0;

return setPath(state, "shouldValidateOnChange", hasErrors);
});
};
const onBlur = event => {
const { target } = event;
const { name } = target;
const parentName = getParentFieldName(name);
const parentName = getParentFieldName(target);

lastFocusedFieldName.current = null;

Expand All @@ -73,8 +71,7 @@ function Form(_props) {
if (
parentName !== lastFocusedFieldName.current &&
(lastMouseDownInputElement.current === null ||
parentName !==
getParentFieldName(lastMouseDownInputElement.current.name))
parentName !== getParentFieldName(lastMouseDownInputElement.current))
) {
setState(state => setPath(state, "namesToValidate", [parentName]));
}
Expand All @@ -98,7 +95,7 @@ function Form(_props) {
*/
if (state.shouldValidateOnChange || isCheckbox) {
newState = setPath(newState, "namesToValidate", [
getParentFieldName(name)
getParentFieldName(target)
]);
}

Expand Down Expand Up @@ -127,8 +124,8 @@ function Form(_props) {
registerField,
unregisterField
};
const validateField = useCallback((state, name) => {
const value = state.values[name];
const validateField = useCallback((values, name) => {
const value = getPath(values, name);
const field = fields.current[name];

if (
Expand Down Expand Up @@ -156,27 +153,22 @@ function Form(_props) {
}, []);
const getNewErrors = useCallback(
state => {
const names = state.namesToValidate;
const newErrors = { ...state.errors };

names.forEach(name => {
const errors = validateField(state, name);
const { namesToValidate, values } = state;

if (errors === null) {
delete newErrors[name];
} else {
newErrors[name] = errors;
}
});
return namesToValidate.reduce((acc, name) => {
const errors = validateField(values, name);

return newErrors;
return errors === null
? deletePath(acc, name)
: setPath(acc, name, errors);
}, state.errors);
},
[validateField]
);
const submitForm = useCallback(() => {
setState(state => ({
...state,
namesToValidate: Object.keys(state.values),
namesToValidate: Object.keys(fields.current),
submitStatus: "VALIDATE_THEN_SUBMIT"
}));
}, []);
Expand Down
5 changes: 4 additions & 1 deletion src/components/Frequency.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ function Frequency(props) {
const inputComponent = (
<InternalInput
name={`${name}.amount`}
parentName={name}
color={color}
type="number"
placeholder={amountPlaceholder}
Expand Down Expand Up @@ -207,6 +208,7 @@ function Frequency(props) {
{frequencyOptions.length > 0 && (
<InternalRadioGroup
name={`${name}.frequency`}
parentName={name}
color={color}
options={frequencyOptions}
columns={2}
Expand All @@ -226,6 +228,7 @@ function Frequency(props) {
<Grid.Item colSpan="1">
<InternalSelect
name={`${name}.frequency`}
parentName={name}
color={color}
optional={optional}
placeholder={selectPlaceholder}
Expand Down Expand Up @@ -260,7 +263,7 @@ Frequency.propTypes = {
disabled: PropTypes.bool,
optional: PropTypes.bool,
validate: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
validateData: PropTypes.object,
validateData: PropTypes.any,
testId: PropTypes.string
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Input.propTypes = {
pasteAllowed: PropTypes.bool,
optional: PropTypes.bool,
validate: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
validateData: PropTypes.object,
validateData: PropTypes.any,
testId: PropTypes.string,
__internal__focus: PropTypes.bool
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/RadioGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ RadioGroup.propTypes = {
onMouseDown: PropTypes.func,
optional: PropTypes.bool,
validate: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
validateData: PropTypes.object,
validateData: PropTypes.any,
testId: PropTypes.string
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Select.propTypes = {
disabled: PropTypes.bool,
optional: PropTypes.bool,
validate: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
validateData: PropTypes.object,
validateData: PropTypes.any,
testId: PropTypes.string,
__internal__focus: PropTypes.bool
};
Expand Down
4 changes: 3 additions & 1 deletion src/components/TimeSpan.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ function TimeSpan(props) {
<Grid.Item colSpan="0">
<InternalInput
name={`${name}.years`}
parentName={name}
color={color}
type="number"
placeholder="Years"
Expand All @@ -162,6 +163,7 @@ function TimeSpan(props) {
<Grid.Item colSpan="1">
<InternalInput
name={`${name}.months`}
parentName={name}
color={color}
type="number"
placeholder="Months"
Expand All @@ -187,7 +189,7 @@ TimeSpan.propTypes = {
disabled: PropTypes.bool,
optional: PropTypes.bool,
validate: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
validateData: PropTypes.object,
validateData: PropTypes.any,
testId: PropTypes.string,
__internal__yearsFocus: PropTypes.bool,
__internal__monthsFocus: PropTypes.bool
Expand Down
3 changes: 3 additions & 0 deletions src/components/internal/InternalCheckbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function InternalCheckbox(_props) {
const props = { ...DEFAULT_PROPS, ..._props };
const {
name,
parentName,
inputId,
color,
disabled,
Expand Down Expand Up @@ -87,6 +88,7 @@ function InternalCheckbox(_props) {
type="checkbox"
id={inputId}
name={name}
data-parent-name={parentName}
checked={value}
disabled={disabled}
onFocus={onFocus}
Expand Down Expand Up @@ -120,6 +122,7 @@ function InternalCheckbox(_props) {

InternalCheckbox.propTypes = {
name: PropTypes.string.isRequired,
parentName: PropTypes.string,
inputId: PropTypes.string.isRequired,
color: PropTypes.oneOf(COLORS),
disabled: PropTypes.bool,
Expand Down
3 changes: 3 additions & 0 deletions src/components/internal/InternalInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function InternalInput(_props) {
const props = { ...DEFAULT_PROPS, ..._props };
const {
name,
parentName,
id,
placeholder,
type,
Expand Down Expand Up @@ -65,6 +66,7 @@ function InternalInput(_props) {
}}
id={id}
name={name}
data-parent-name={parentName}
placeholder={placeholder}
type={type}
min={min}
Expand All @@ -88,6 +90,7 @@ function InternalInput(_props) {

InternalInput.propTypes = {
name: PropTypes.string.isRequired,
parentName: PropTypes.string,
id: PropTypes.string,
placeholder: PropTypes.string,
type: PropTypes.oneOf(TYPES),
Expand Down
12 changes: 9 additions & 3 deletions src/components/internal/InternalRadioGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ RadioCircle.propTypes = {
};

function Radio({
name,
parentName,
color,
isOneLine,
showCircle,
name,
label,
isChecked,
disabled,
Expand All @@ -74,6 +75,7 @@ function Radio({
type="radio"
id={inputId}
name={name}
data-parent-name={parentName}
value={value}
checked={isChecked}
disabled={disabled}
Expand Down Expand Up @@ -110,10 +112,11 @@ function Radio({
}

Radio.propTypes = {
name: PropTypes.string.isRequired,
parentName: PropTypes.string,
color: PropTypes.oneOf(COLORS).isRequired,
isOneLine: PropTypes.bool.isRequired,
showCircle: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
isChecked: PropTypes.bool.isRequired,
disabled: PropTypes.bool.isRequired,
Expand All @@ -128,6 +131,7 @@ function InternalRadioGroup(_props) {
const props = { ...DEFAULT_PROPS, ..._props };
const {
name,
parentName,
labelId,
options,
columns,
Expand Down Expand Up @@ -159,10 +163,11 @@ function InternalRadioGroup(_props) {
key={value}
>
<Radio
name={name}
parentName={parentName}
color={color}
isOneLine={cols === options.length}
showCircle={showCircles}
name={name}
label={label}
value={value}
isChecked={value === checkedValue}
Expand All @@ -181,6 +186,7 @@ function InternalRadioGroup(_props) {

InternalRadioGroup.propTypes = {
name: PropTypes.string.isRequired,
parentName: PropTypes.string,
labelId: PropTypes.string,
options: PropTypes.arrayOf(
PropTypes.shape({
Expand Down
3 changes: 3 additions & 0 deletions src/components/internal/InternalSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function InternalSelect(_props) {
const props = { ...DEFAULT_PROPS, ..._props };
const {
name,
parentName,
id,
color,
placeholder,
Expand Down Expand Up @@ -66,6 +67,7 @@ function InternalSelect(_props) {
}}
id={id}
name={name}
data-parent-name={parentName}
aria-invalid={isValid ? null : "true"}
aria-describedby={describedBy}
disabled={disabled}
Expand Down Expand Up @@ -98,6 +100,7 @@ function InternalSelect(_props) {

InternalSelect.propTypes = {
name: PropTypes.string.isRequired,
parentName: PropTypes.string,
id: PropTypes.string,
color: PropTypes.oneOf(COLORS),
placeholder: PropTypes.string,
Expand Down
Loading

0 comments on commit 0f1cd88

Please sign in to comment.