Search code examples
javascriptreactjsloopsvalidationjsx

JSX/React - how to loop through an array inside a component prop


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);
    };

Solution

  • 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"
    />