Search code examples
javascriptreactjstrimdynamic-forms

React dynamic form input fields typeError undefined trim()


I have a React form with dynamic input fields that a user can add and remove input fields. All inputs are required. If all input fields are filled and the form is submitted then i console.log() an array with the values, else empty input gets focused. The problem is that if i whitespace(backspace) inside input fields, the form can be submitted and i don't want this. How can i fix this? I tried to use the trim() method (const registriesIsValid = registryValues.every((registry)=>registry.name.trim() !== "" && registry.code.trim() !== "");) but im getting "typeError Cannot read properties of undefined (reading 'trim')".

typeError

Below my code without trim method.

Edit react dynamic form input fields

App.js

import React, { useState, useRef } from "react";
import Wrapper from "./Wrapper";
import "./styles.css";

export default function App() {
  const [registryValues, setRegistryValues] = useState([]);
  const [registryValuesTouched, setRegistryValuesTouched] = useState(false);
  const fieldRef = useRef();
  // const registriesRef = useRef();

  const registriesIsValid = registryValues.every(
    (registry) => registry.name !== "" && registry.code !== ""
  );
  const registriesInputIsInvalid = !registriesIsValid;

  let handleRegistryChange = (i, event) => {
    setRegistryValuesTouched(true);
    let newRegistryValues = [...registryValues];
    if (event.target.name === "name") {
      newRegistryValues[i].registry["name"] = event.target.value;
    }
    if (event.target.name === "code") {
      newRegistryValues[i].code = event.target.value;
    }
    // newRegistryValues[i][event.target.name] = event.target.value;
    setRegistryValues(newRegistryValues);
  };

  let addRegistryFields = (event) => {
    event.preventDefault();
    setRegistryValues([
      ...registryValues,
      { registry: { name: "" }, code: "" }
    ]);
  };

  let removeRegistryFields = (i, event) => {
    event.preventDefault();
    let newRegistryValues = [...registryValues];
    newRegistryValues.splice(i, 1);
    setRegistryValues(newRegistryValues);
  };

  const submitHandler = (event) => {
    event.preventDefault();
    setRegistryValuesTouched(true);

    for (let elem of fieldRef.current.elements) {
      if (elem.dataset.required && !elem.value) {
        elem.focus();
        return;
      }
    }

    console.log(registryValues);

    setRegistryValuesTouched(false);
  };

  return (
    <Wrapper>
      <form onSubmit={submitHandler} ref={fieldRef}>
        <fieldset>
          <legend>
            <h3 className="govgr-heading-m margin-top">Registries</h3>
          </legend>
          <div
            className={`${
              registriesInputIsInvalid ? "govgr-form-group__error" : ""
            }`}
          >
            {registriesInputIsInvalid && (
              <p className="govgr-error-message">
                <span className="govgr-visually-hidden">Λάθος:</span>All fields
                are required.
              </p>
            )}
            {registriesInputIsInvalid && (
              <p className="govgr-error-message">
                <span className="govgr-visually-hidden">Λάθος:</span>You must
                fill in registry Name and registry Code.
              </p>
            )}
            {registryValues.map((element, index) => (
              <div key={index} className="flex-row registry-margin-bottom">
                <div className="registry-flex-basis">
                  <div className="govgr-form-group">
                    <label
                      className="govgr-label govgr-!-font-weight-bold"
                      htmlFor="name"
                    >
                      Registry Name*
                    </label>
                    <input
                      className={`govgr-input govgr-!-width-three-quarter ${
                        element.registry["name"].trim() === ""
                          ? "govgr-error-input"
                          : ""
                      }`}
                      type="text"
                      name="name"
                      data-required="true"
                      value={element.registry["name"] || ""}
                      onChange={(e) => handleRegistryChange(index, e)}
                    />
                  </div>

                  <div className="govgr-form-group">
                    <label
                      className="govgr-label govgr-!-font-weight-bold"
                      htmlFor="code"
                    >
                      Registry Code*
                    </label>
                    <input
                      className={`govgr-input govgr-!-width-three-quarter ${
                        element.code.trim() === "" ? "govgr-error-input" : ""
                      }`}
                      type="text"
                      name="code"
                      data-required="true"
                      value={element.code || ""}
                      onChange={(e) => handleRegistryChange(index, e)}
                    />
                  </div>
                </div>
                <button
                  className="govgr-btn govgr-btn-warning remove-registry"
                  onClick={(e) => removeRegistryFields(index, e)}
                >
                  X
                </button>
              </div>
            ))}
          </div>
        </fieldset>
        <button
          className="govgr-btn govgr-btn-secondary button-registry"
          onClick={addRegistryFields}
        >
          Add Registry
        </button>

        <button
          className="govgr-btn govgr-btn-primary btn-center"
          type="submit"
        >
          Save
        </button>
      </form>
    </Wrapper>
  );
}

Solution

  • I fixed it. Actually, inside my submitHandler i forgot to check (if !registriesIsValid){...} .

    Also, my registry object has different structure. So i change validation to:

    const registriesIsValid = registryValues.every((registry)=>registry.registry["name"].trim() !== "" && registry.code.trim() !== "");

    App.js

    import React, { useState, useRef } from "react";
    import Wrapper from "./Wrapper";
    import "./styles.css";
    
    export default function App() {
      const [registryValues, setRegistryValues] = useState([]);
      const [registryValuesTouched, setRegistryValuesTouched] = useState(false);
      const fieldRef = useRef();
      // const registriesRef = useRef();
       
      //1st change
      const registriesIsValid = registryValues.every(
        (registry) => registry.registry["name"].trim() !== "" && registry.code.trim() !== ""
      );
      const registriesInputIsInvalid = !registriesIsValid;
    
      let handleRegistryChange = (i, event) => {
        setRegistryValuesTouched(true);
        let newRegistryValues = [...registryValues];
        if (event.target.name === "name") {
          newRegistryValues[i].registry["name"] = event.target.value;
        }
        if (event.target.name === "code") {
          newRegistryValues[i].code = event.target.value;
        }
        // newRegistryValues[i][event.target.name] = event.target.value;
        setRegistryValues(newRegistryValues);
      };
    
      let addRegistryFields = (event) => {
        event.preventDefault();
        setRegistryValues([
          ...registryValues,
          { registry: { name: "" }, code: "" }
        ]);
      };
    
      let removeRegistryFields = (i, event) => {
        event.preventDefault();
        let newRegistryValues = [...registryValues];
        newRegistryValues.splice(i, 1);
        setRegistryValues(newRegistryValues);
      };
    
      const submitHandler = (event) => {
        event.preventDefault();
        setRegistryValuesTouched(true);
    
    
        //2nd change
        if (!registriesIsValid) {
          for (let elem of fieldRef.current.elements) {
            if (elem.dataset.required && !elem.value.trim()) {
              elem.focus();
              return;
            }
          }
          return;
        }
        
        console.log(registryValues);
    
        setRegistryValuesTouched(false);
      };
    
      return (
        <Wrapper>
          <form onSubmit={submitHandler} ref={fieldRef}>
            <fieldset>
              <legend>
                <h3 className="govgr-heading-m margin-top">Registries</h3>
              </legend>
              <div
                className={`${
                  registriesInputIsInvalid ? "govgr-form-group__error" : ""
                }`}
              >
                {registriesInputIsInvalid && (
                  <p className="govgr-error-message">
                    <span className="govgr-visually-hidden">Λάθος:</span>All fields
                    are required.
                  </p>
                )}
                {registriesInputIsInvalid && (
                  <p className="govgr-error-message">
                    <span className="govgr-visually-hidden">Λάθος:</span>You must
                    fill in registry Name and registry Code.
                  </p>
                )}
                {registryValues.map((element, index) => (
                  <div key={index} className="flex-row registry-margin-bottom">
                    <div className="registry-flex-basis">
                      <div className="govgr-form-group">
                        <label
                          className="govgr-label govgr-!-font-weight-bold"
                          htmlFor="name"
                        >
                          Registry Name*
                        </label>
                        <input
                          className={`govgr-input govgr-!-width-three-quarter ${
                            element.registry["name"].trim() === ""
                              ? "govgr-error-input"
                              : ""
                          }`}
                          type="text"
                          name="name"
                          data-required="true"
                          value={element.registry["name"] || ""}
                          onChange={(e) => handleRegistryChange(index, e)}
                        />
                      </div>
    
                      <div className="govgr-form-group">
                        <label
                          className="govgr-label govgr-!-font-weight-bold"
                          htmlFor="code"
                        >
                          Registry Code*
                        </label>
                        <input
                          className={`govgr-input govgr-!-width-three-quarter ${
                            element.code.trim() === "" ? "govgr-error-input" : ""
                          }`}
                          type="text"
                          name="code"
                          data-required="true"
                          value={element.code || ""}
                          onChange={(e) => handleRegistryChange(index, e)}
                        />
                      </div>
                    </div>
                    <button
                      className="govgr-btn govgr-btn-warning remove-registry"
                      onClick={(e) => removeRegistryFields(index, e)}
                    >
                      X
                    </button>
                  </div>
                ))}
              </div>
            </fieldset>
            <button
              className="govgr-btn govgr-btn-secondary button-registry"
              onClick={addRegistryFields}
            >
              Add Registry
            </button>
    
            <button
              className="govgr-btn govgr-btn-primary btn-center"
              type="submit"
            >
              Save
            </button>
          </form>
        </Wrapper>
      );
    }