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.
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!
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.
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>
key
prop to be passed when mapping over objects (hence I've added it for each wrapper div
and option
element.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 everywhereconsole.log
ing the formState
to demonstrate the changes as you update the form.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!