Home > front end >  How do I iterate, dynamically load my form input elements and then retrieve the input values on Form
How do I iterate, dynamically load my form input elements and then retrieve the input values on Form

Time:11-12

I am creating a sample dynamic form and I want to load my input elements based on a JSON which contains different input elements like "textbox", "text-area", "dropdown", "radio-input" and so on.. I have a JSON file created to get this as shown below:

[ 
    {
        "id": "1",
        "type": "textbox",
        "text": "",
        "required": true,
        "label": "lbl"
    },
    {
        "id": "2",
        "type": "textbox",
        "text": "",
        "required": true,
        "label": "Specification Name"
    },
    {
        "id": "3",
        "type": "dropdown",
        "text": "",
        "required": true,
        "label": "Specification Reviewed",
        "options":["a","2"]
    },
    {
        "id": "4",
        "type": "dropdown",
        "text": "",
        "required": true,
        "label": "Action Required",
        "options":["1","2","3"]
    },
    {
        "id": "5",
        "type": "textbox",
        "text": "",
        "required": true,
        "label": "lbl"
    }
]

I have an App base component which calls another component called "Input" which has my layout and I retrieve the elements through that component. I am able to pull the text box and dropdown here but I am not able to iterate through the dropdown select. I'm not sure how to do it.

Here's my App Base solution: Here I use the map concept to fetch the data from the JSON local file and assign it to inputvalues which I then use in the return within the form tag.

  1. I'm able to list all my input elements dynamically
  2. But I'm not able to get the dropdown values from my JSON file
    function App() {
      const [inputObject, setInputObject] = React.useState(inputData)
      const inputvalues = inputObject.map( input => {
        return (
            <Input 
                    key={input.id}
                    input={input}
                    />
        )
    })
    
    const handleSubmit = (event) => {
      event.preventDefault();
    }
    
      return (
        <div className="App">
          <header className="App-header">
            <form>
            <div>
              {inputvalues}
            </div>
            <input type="submit" value="submit" onClick={handleSubmit} />
          </form>
          </header>
        </div>
      );
    }

export default App;

And, here's my input.js component file: This basically lays out the input elements and I fetch the data using Props but I am unable to fetch the dropdown selection values because I would need to somehow iterate within each of those dropdown elements.

export default function Input(props) {
const [state, setState] = React.useState({
    textBoxValue: ""
  })

  function handleChange(evt) {
    setState({ [props.input.id] : evt.target.value });
  }
   if (props.onChange) {
     props.onChange(state);
      }
return (
    <div>
        <label>{props.input.type}: </label>
        {props.input.type === "textbox" && <input name={props.input.type} placeholder={props.input.type} id={props.input.id} value={state.firstName} onChange={handleChange}/>}
        {props.input.type === "dropdown" && <select name={props.input.type} id={props.input.id}>
            <option value={props.input.options}></option></select>
}</div>)}

Please help me or guide me because I'm still learning React. In addition to this, how would i later get all the input values upon FORM SUBMIT ? For this I tried adding a handleChange event to see if data comes through but it does not work.

Thank you so much in advance!

CodePudding user response:

You may find Yup and Formik useful.
With Yup, you can include types to fields as well as things such as if the field is required.
The example linked should get you in the right direction.

Edit - (after OP comment)

So without using any external library, you could do something like this:

// Get a hook function
const {useState} = React;

const INPUTS = [ 
  {
      "id": "1",
      "type": "textbox",
      "value": "",
      "required": true,
      "label": "lbl"
  },
  {
      "id": "2",
      "type": "textbox",
      "value": "",
      "required": true,
      "label": "Specification Name"
  },
  {
      "id": "3",
      "type": "dropdown",
      "value": "",
      "required": true,
      "label": "Specification Reviewed",
      "options":["a","2"]
  },
  {
      "id": "4",
      "type": "dropdown",
      "value": "",
      "required": true,
      "label": "Action Required",
      "options":["1","2","3"]
  },
  {
      "id": "5",
      "type": "textbox",
      "value": "",
      "required": true,
      "label": "lbl"
  }
];

const convertArrayToObject = (array, key, targetKey) => {
  const initialValue = {};
  return array.reduce((obj, item) => {
    return {
      ...obj,
      [item[key]]: item[targetKey],
    };
  }, initialValue);
};

const Form = () => {
  const [formState, setFormState] = useState(
    convertArrayToObject(INPUTS, "id", "value")
  );
  const handleChanges = (keyName) => (e) => {
    const newValue = e.target.value
    setFormState(prev => ({
      ...prev,
      [keyName]: newValue
    }));
  }
  
  console.log(formState);
  
  return (
    <form>
      {INPUTS.map((input, inputIndex) => (
        <div key={inputIndex}>
          <label htmlFor={input.id}>{input.label}</label>
          {input.type === "dropdown" && input.options ? (
              <select onChange={handleChanges(input.id)}>
                {input.options.map((option, optionIndex) => (
                  <option
                    key={optionIndex}
                    value={option}>{option}
                  </option>
                ))}
              </select>
            ) : (
            <input
              id={input.id}
              name={input.id}
              required={input.required}
              type={input.type}
              onChange={handleChanges(input.id)}
              value={formState.value}
              />
          )}
        </div>
      ))}
    </form>
  );
}


ReactDOM.createRoot(
  document.getElementById("root")
).render(
  <Form />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

A little reasoning behind some of the code written:

  • React wants a key prop to be passed when mapping over objects (hence I've added it for each wrapper div and option element.
  • I've mapped over the INPUTS object to build the initial state, and then created an onChange handler that is curried, that way it is generic enough to be used everywhere
  • I'm just console.loging the formState to demonstrate the changes as you update the form.

Beyond

You will of course need some kind of some kind of submit button if you plan to submit the data to an API.

<button type="submit">Submit</button

But I will leave that as an exercise for you...

Hope this helps!

  • Related