Reputation: 113
I have a form for a project where the description is required, and if they want to add details, those textareas need to be filled out. But it's okay if there are no details added. So if they want to add a detail, that text area has to be filled. But an empty array is allowed.
I am having trouble overwriting the default error for the missing details, the default being "details[0]" must not be a sparse array
.
Schema:
const descriptionDetailSchema = Joi.object({
description: Joi.string().required().messages({
'string.base': 'Description is required',
'string.empty': 'Description is required'
}),
details: Joi.array().items(
Joi.string().messages({
'string.empty': 'Detail is required'
})
)
});
const DescriptionAndDetailForm = forwardRef(
({ activityIndex, item, index, saveDescription, setFormValid }, ref) => {
DescriptionAndDetailForm.displayName = 'DescriptionAndDetailForm';
const {
handleSubmit,
control,
formState: { error, errors, isValid, isValidating },
getValues
} = useForm({
defaultValues: {
description: item.description,
details: item.details
},
mode: 'onBlur',
reValidateMode: 'onBlur',
resolver: joiResolver(descriptionDetailSchema)
});
useEffect(() => {
console.log('isValid changed');
console.log({ errors, isValid, isValidating });
setFormValid(isValid);
}, [isValid]);
useEffect(() => {
console.log('errors changed');
console.log({ errors, isValid, isValidating });
}, [errors]);
useEffect(() => {
console.log('isValidating changed');
const { error, value } = descriptionDetailSchema.validate(getValues());
console.log({ error, value, errors, isValid, isValidating });
}, [isValidating, errors]);
const initialState = item;
function reducer(state, action) {
switch (action.type) {
case 'updateField':
return {
...state,
[action.field]: action.value
};
case 'addDetail': {
const newDetail = newDescriptionDetail();
return {
...state,
details: [...state.details, newDetail]
};
}
case 'removeDetail': {
const detailsCopy = [...state.details];
detailsCopy.splice(action.index, 1);
return {
...state,
details: detailsCopy
};
}
case 'updateDetails': {
const detailsCopy = [...state.details];
detailsCopy[action.detailIndex].detail = action.value;
return {
...state,
details: detailsCopy
};
}
default:
throw new Error(
'Unrecognized action type provided to DescriptionAndDetailForm reducer'
);
}
}
const [state, dispatch] = useReducer(reducer, initialState);
const handleDescriptionChange = e => {
dispatch({
type: 'updateField',
field: 'description',
value: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
saveDescription(activityIndex, index, state);
handleSubmit(e);
};
const handleAddDetail = () => {
dispatch({ type: 'addDetail' });
};
const handleDeleteDetail = (descriptionIndex, detailIndex) => {
dispatch({ type: 'removeDetail', index: detailIndex });
};
const handleDetailChange = (e, i) => {
dispatch({
type: 'updateDetails',
detailIndex: i,
value: e.target.value
});
};
return (
<form
index={index}
key={`activity${activityIndex}-index${index}-form`}
onSubmit={onSubmit}
>
<Controller
key={`activity${activityIndex}-index${index}`}
name="description"
control={control}
render={({ field: { onChange, ...props } }) => (
<TextField
{...props}
label="Description"
multiline
rows="4"
onChange={e => {
handleDescriptionChange(e);
onChange(e);
}}
errorMessage={errors?.description?.message}
errorPlacement="bottom"
/>
)}
/>
{state.details.map(({ key, detail }, i) => (
<Review
key={key}
onDeleteClick={ () => handleDeleteDetail(index, i) }
onDeleteLabel="Remove"
skipConfirmation
ariaLabel={`${i + 1}. ${detail}`}
objType="Detail"
>
<div>
<Controller
name={`details.${i}`}
control={control}
render={({ field: { onChange, ...props } }) => (
<TextField
{...props}
id={`${activityIndex}-detail${i}`}
name={`details.${i}`}
label="Detail"
value={detail}
multiline
rows="4"
onChange={e => {
handleDetailChange(e, i);
onChange(e);
}}
errorMessage={errors?.details && errors?.details[i]?.message}
errorPlacement="bottom"
/>
)}
/>
</div>
</Review>
))}
<div>
<Button
key={`activity${activityIndex}-index${index}-add-metric`}
onClick={handleAddDetail}
>
<Icon icon={faPlusCircle} />
Add Detail
</Button>
</div>
<input
type="submit"
ref={ref}
hidden
/>
</form>
);
}
);
Upvotes: 1
Views: 689
Reputation: 56
You need to make sure your schema is returning a value if you're using .custom() for validation
Upvotes: 0