I have a dynamic form as a functional component which is generated via a class based component. I want to make reset button which clears the input field values and sets the state to null array.
Full code is available here: https://codesandbox.io/s/beautiful-archimedes-o1ygt
I want to make a reset button, clearing all the input values and initializing the Itemvalues array to null. Even if I set the values to null, it doesn't clear the input field.
However, the problem I'm facing is that since, it is a dynamic form and a functional component it doesn't have a predefined state for each individual form field making it difficult to set value to null. Can someone please help, I'm stuck on this from a long time
Here's a codesandbox to show you how to reset the items: https://codesandbox.io/s/romantic-heisenberg-93qi7
I also left a note for you on how to get this to work with your API data, see the comment inside onChangeText()
The problem is that the inputs are not controlled by state as you have deduced. We should create an updated object for each item from your API, giving it a value
prop.
import React from "react";
import ReactDOM from "react-dom";
import Cart from "./Cart";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
Items: [],
itemvalues: [{}]
};
this.onChangeText = this.onChangeText.bind(this);
this.getItems = this.getItems.bind(this);
this.handleReset = this.handleReset.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.findFieldIndex = this.findFieldIndex.bind(this);
this.trimText = this.trimText.bind(this);
}
getItems = () => {
/*if the data is coming from an API, store it in an array then .map() over it.
we can add a value prop to the object like:
so you can do something like:
const newItems = [...apiData].map((item) => {
return {
...item,
value: ""
}
})
this.setState({
Items: newItems
})
*/
this.setState({
Items: [
{
name: "item1",
description: "item1",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item2",
description: "item2",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item3",
description: "item3",
group: "groupB",
dtype: "str",
value: ""
},
{
name: "item4",
description: "item4",
group: "groupB",
dtype: "str",
value: ""
}
]
});
};
onChangeText = e => {
const updatedItems = [...this.state.Items].map(item => {
if (item.name === e.target.name) {
return {
...item,
value: e.target.value
};
} else {
return item;
}
});
const updatedItemValues = [...updatedItems].reduce((obj, curr) => {
if (!obj[curr.group]) {
obj[curr.group] = [];
}
obj[curr.group] = [...obj[curr.group], { [curr.name]: curr.value }];
return obj;
}, {});
this.setState({
...this.state,
Items: updatedItems,
itemvalues: updatedItemValues
});
};
findFieldIndex = (array, name) => {
return array.findIndex(item => item[name] !== undefined);
};
trimText(str) {
return str.trim();
}
handleReset = () => {
const resetedItems = [...this.state.Items].map(item => {
return {
...item,
value: ""
};
});
this.setState(
{
...this.state,
Items: resetedItems,
itemvalues: []
},
() => console.log(this.state)
);
};
handleSubmit = () => {
console.log(this.state.itemvalues);
};
render() {
return (
<div>
{
<Cart
Items={this.state.Items}
getItems={this.getItems}
handleSubmit={this.handleSubmit}
handleReset={this.handleReset}
onChangeText={this.onChangeText}
/>
}
</div>
);
}
}
import React, { useEffect } from "react";
import Form from "./Form";
const Cart = props => {
useEffect(() => {
props.getItems(props.Items);
}, []);
return (
<div>
<Form Items={props.Items} onChangeText={props.onChangeText} />
<button onClick={props.handleSubmit}>Submit</button>
<button onClick={props.handleReset}>Reset</button>
</div>
);
};
export default Cart;
The Cart component can remain mostly the same, we do not need to pass in props.items
to useEffect()
dependency.
import React from "react";
const Form = props => {
return (
<div>
{props.Items.map(item => {
return (
<input
name={item.name}
placeholder={item.description}
data-type={item.dtype}
data-group={item.group}
onChange={e => props.onChangeText(e)}
value={item.value}
/>
);
})}
</div>
);
};
export default Form;
Now in Form
component, we provide each input a value prop that is connected to the item our upper-most parent component-state.
That's pretty much all you need to reset the values.
See if that works for you:
Working example on CodeSandbox
Since you were already using hooks in part of your code, I've converted your class into a functional component using hooks (my advice: learn hooks and forget about class components).
I've added a value
property to your INITIAL_STATE
so it will keep the input value for each inputItem
.
Full CODE:
index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import FormV2 from "./FormV2";
import "./styles.css";
function App() {
const INITIAL_STATE = [
{
name: "item1",
description: "item1",
group: "groupA",
dtype: "str",
value: "" // ADDED VALUE PROPERTY TO KEEP THE INPUT VALUE
},
{
name: "item2",
description: "item2",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item3",
description: "item3",
group: "groupB",
dtype: "str",
value: ""
},
{
name: "item4",
description: "item4",
group: "groupB",
dtype: "str",
value: ""
}
];
const [inputItems, setInputItems] = useState(INITIAL_STATE);
function handleChange(event, index) {
const newValue = event.target.value;
setInputItems(prevState => {
const aux = Array.from(prevState);
aux[index].value = newValue;
return aux;
});
}
function handleReset() {
console.log("Reseting Form to INITIAL_STATE ...");
setInputItems(INITIAL_STATE);
}
function handleSubmit() {
inputItems.forEach(item =>
console.log(
"I will submit input: " + item.name + ", which value is: " + item.value
)
);
}
return (
<FormV2
handleSubmit={handleSubmit}
handleReset={handleReset}
handleChange={handleChange}
inputItems={inputItems}
/>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
FormV2.js
import React from "react";
function FormV2(props) {
const formInputItems = props.inputItems.map((item, index) => (
<div key={item.name}>
{item.name + ": "}
<input
type="text"
data-type={item.dtype}
data-group={item.group}
placeholder={item.description}
value={item.value}
onChange={event => props.handleChange(event, index)}
/>
</div>
));
return (
<React.Fragment>
<form>{formInputItems}</form>
<button onClick={props.handleSubmit}>Submit</button>
<button onClick={props.handleReset}>Reset</button>
<div>State: {JSON.stringify(props.inputItems)}</div>
</React.Fragment>
);
}
export default FormV2;
In order to control the values of the child components (Items) which I presume are input fields you need to be passing down their values from their parent component. So each of your items will have an item.value
which is stored in the parent component's state.
That means that in the parent component you will be able to define a method which clears all of the item values it is storing in its state. That will probably look something like
resetInputs = () => {
this.setState({
inputFields: this.state.inputFields.map(inputField => {
...inputField,
value: ''
}
})
}
Also you'll need to write what kind of tag you want for your code to work, like input.
So what you'll end up with for the code of the child component you shared is something like:
const Form = (props) => {
return (
<div>
{props.Items.map(item => (
<input
name={item.name}
value={item.value}
placeholder={item.description}
onChange={e => props.onChangeText(e)}
/>
)
)}
</div>
);
}
export default Form
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With