I have a React app with a form containing multiple input fields and checkboxes. If fields are blank or unchecked I have individual functions that check each of those respective fields to determine if they’re blank or unchecked before proceeding with an API call to submit the data.
I call those check functions when the form button is clicked. If the check passes the API call is made, if it fails I’d like to display a list of errors that the user can correct. The button is clicked again, the errors are cleared and rechecked, etc.
Where I’m struggling is with maintaining the state of an array where I keep the list of errors. The errors are inserted into the array on each function call and then mapped and listed out.
Problem: Only the last error is showing in the array instead of all of them. The clear function isn’t clearing the array. On the first click of the submit button, the error is appearing as intended, but the console.log(errorArray) is showing an empty array.
export default function ProfileForm(props) {
const [formErrors, setFormErrors] = useState([]);
const hasLinkRequest = () => {
if(linkRequest !== ""){
return true
} else {
const addLinkError = [...formErrors, "Missing Link Request"];
setFormErrors(addLinkError);
return true;
}
};
const hasStringFields = () => {
if (
pocFirstName !== "" &&
pocLastName !== "" &&
email !== "" &&
phoneNumber !== "" &&
streetAddress1 !== "" &&
city !== "" &&
state !== "" &&
zipcode !== "" &&
zipcode.length == 5
){
return true
} else {
const addFieldError = [...formErrors, "Missing Required Field"];
setFormErrors(addFieldError);
return true
}
};
const hasCraftsmanship = () => {
var crafts = [];
Object.values(craftsmanships)
.sort(sortCraftsmanshipFn)
.map(({id, name, has}) => {
if(has == true) {
crafts.push(crafts);
}
});
if(crafts.length > 0){
return true
} else {
const addSkillError = [...formErrors, "Missing Service Types"];
setFormErrors(addSkillError);
return true
}
};
const clearErrors = () => {
console.log(formErrors);
const emptyArray = [];
setFormErrors(emptyArray);
console.log(formErrors);
}
const canSubmit = () => {
clearErrors();
hasStringFields();
hasLinkRequest();
hasCraftsmanship();
if(formErrors.length > 0) {
return true
} else {
return false
}
};
const onFormSubmit = (e) => {
e.preventDefault();
if (canSubmit()) {
run API call
} else {
"we display the list of errors"
}
}
<FORM CONTENT>
<ErrorAlert errors={formErrors} />
<Button onClick={onFormSubmit} />
</END OF FORM>
}
function ErrorAlert(props) {
const { errors = {} } = props;
return (
<div className='div-block-34'>
{Object.keys(errors || {}).map((key) => (
<div className='text-block-12' key={key}>{`${errors[key]}`}</div>
))}
</div>
);
}
When I click the button, I’d like the array to store all of the applicable error messages. Right now, it only displays "Missing Service Types" the first time I click the button. Each subsequent click keeps adding more "Missing Service Types" to the formErrors array. If I update the Service Type field, then re-click it, it shows the "Missing Link Request" error along with the previous error. So it’s only updating the array with the last function in the canSubmit function.
I’m not showing the full form and fields as the form works fine, it’s just this error handling.
2
Answers
To avoid concurrency affectation, you’ve to take the old state, not the current.
For exemple:
Instead of :
You have to use callback function with useState.
It is not working because you are using the formErrors variable directly, and adding new error to it. It holds the value from the state in the last render. Hence when you call all four function in sequence synchronously, and then you are creating a new array out of this array (without mutating the same array). It always update the state with previous state of formErrors.
This is what happen on clicking submit button
On First Submit
Hence after running all three functions it is set to
["Missing Service Types"]
On 2nd Submit
Hence after running all 3 function it is set to
["Missing Service Types", "Missing Service Types"]
To understand this better I am adding another way of doing it (Not recommended)
This will work since you are updating the same variable which was is going to be used in next function. Hence it will hold the value from previous function.