This one's a bit confusing so apologies.
I have a component, PersonalInfo.jsx, that has the components Input.jsx (three of them) and Button.jsx within it. PersonalInfo has the state of [valid, setValid] = useState([]).
When the button is clicked, it runs a validation function. If the name validation fails, it pushes "nameFalse" to the valid state array. If the email validation fails, it pushes "emailFalse", etc.
Each Input component has a "valid" attribute with a conditional that if valid is false, a className is added within with red text to signify something is wrong.
What I can do, is have the valid state set as a boolean, and when a validation fails, it setsValid to false (instead of pushing a string to an array). This triggers the conditional and the red text className activates. The problem is that, that will set each Input.jsx valid state to false, so if only the name validation fails, all three inputs will appear as if they failed.
The idea was to have a string ("nameFalse", "emailFalse", etc.) pushed to the valid state array and loop through that array in the component's conditional prop to see if a string matches, and if it does, trigger the red text className. I'm not sure if I can run a loop/function inside a prop attribute.
PersonalInfo.jsx relevant code:
const [valid, setValid] = useState([]);
<Input
id="nameInput"
label="Name"
errorLabel="Name - Please enter a valid name"
type="text"
// This is the conditional that will trigger the red text if validation fails
// Can I loop through valid here and try to match a string?
valid={... ? false : true}
onChange={(e) => setNameValue(e.target.value)}
value={nameValue}
placeholder="e.g. Stephen King"
/>
<Button
setValid={setValid}
nameValue={nameValue}
valid={valid}
emailValue={emailValue}
step={step}
setStep={setStep}
/>
Relevant Input.jsx:
<div className="flex flex-col">
<label
htmlFor={id}
className={`${
valid
? "text-marine-blue text-xs font-bold mt-3 md:mt-5"
: "text-red-500 text-xs font-bold mt-3 md:mt-5"
}`}
>
{valid ? label : errorLabel}
</label>
<input
type={type}
id={id}
className="border-2 p-2 rounded-md mt-1"
placeholder={placeholder}
onChange={onChange}
value={value}
required
/>
</div>
Relevant Button.jsx
const handleNext = () => {
if (!validateName({ nameValue })) {
console.log("name is false");
setValid(valid.push("nameFalse"));
}
if (!validateEmail({ emailValue })) {
console.log("email is false");
setValid(valid.push("emailFalse"));
}
// setStep(step + 1);
};
Instead of an array of booleans or "isValid..." strings, use an object with keys aligning to the field names with values being the error messages. Now instead of trying to loop through an array for a matching element when rendering, you get an O(1)
lookup in a map object for a specific property.
Example:
const [errors, setErrors] = useState({});
...
const handleNext = () => {
const errors = {};
if (!validateName({ nameValue })) {
errors.nameInput = "Name - Please enter a valid name";
}
if (!validateEmail({ emailValue })) {
errors.email = "......."
}
// ... etc
if (Object.keys(errors).length) {
// if there are errors, update state
setErrors(errors);
}
};
...
<Input
id="nameInput"
label="Name"
errorLabel={errors.nameInput} // <-- set the error label/message
type="text"
valid={!errors.nameInput} // <-- if no error property, then valid
onChange={(e) => setNameValue(e.target.value)}
value={nameValue}
placeholder="e.g. Stephen King"
/>