Sorry for showing wrong use-case. All inputs inside the Form
are being passed though this.props.children
, and they can be situated at any deep point of the components tree, so the approach of passing the handleChange
directly to inputs will not work at all.
Here is code snippet with the reproduction of the problem.
class CustomSelect extends React.Component {
items = [
{ id: 1, text: "Kappa 1" },
{ id: 2, text: "Kappa 2" },
{ id: 3, text: "Kappa 3" }
]
state = {
selected: null,
}
handleSelect = (item) => {
this.setState({ selected: item })
}
render() {
var { selected } = this.state
return (
<div className="custom-select">
<input
name={this.props.name}
required
style={{ display: "none" }} // or type="hidden", whatever
value={selected
? selected.id
: ""
}
onChange={() => {}}
/>
<div>Selected: {selected ? selected.text : "nothing"}</div>
{this.items.map(item => {
return (
<button
key={item.id}
type="button"
onClick={() => this.handleSelect(item)}
>
{item.text}
</button>
)
})}
</div>
)
}
}
class Form extends React.Component {
handleChange = (event) => {
console.log("Form onChange")
}
render() {
return (
<form onChange={this.handleChange}>
{this.props.children}
</form>
)
}
}
ReactDOM.render(
<Form>
<label>This input will trigger form's onChange event</label>
<input />
<CustomSelect name="kappa" />
</Form>,
document.getElementById("__root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="__root"></div>
As you can see, when you type something in default input (controlled or uncontrolled, whatever), form catches bubbled onChange
event. But when you are setting the value of the input programmatically (with the state
, in this case), the onChange event is not being triggered, so I cannot catch this changes inside the form's onChange
.
Is there any options to beat this problem? I tried to input.dispatchEvent(new Event("change", { bubbles: true }))
immediately after setState({ selected: input })
and inside it's callback, but there is no result.
The onchange event occurs when the value of an element has been changed. For radiobuttons and checkboxes, the onchange event occurs when the checked state has been changed.
Definition and Usage The difference is that the oninput event occurs immediately after the value of an element has changed, while onchange occurs when the element loses focus, after the content has been changed.
I really think the best to do what you try to do is first make sure to control each individual input. Keep those values in state and just working with the onSubmit event from the form. React even recommended this approach here https://reactjs.org/docs/uncontrolled-components.html
In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.
You can read about controlled here https://reactjs.org/docs/forms.html#controlled-components
If you want to see how I will have made with just control this will have been looks like that https://codesandbox.io/s/2w9qnk8lxp You can see if you click enter the form submit event with the value keep in state.
class CustomSelect extends React.Component {
items = [
{ id: 1, text: "Kappa 1" },
{ id: 2, text: "Kappa 2" },
{ id: 3, text: "Kappa 3" }
];
render() {
return (
<div className="custom-select">
<div>
Selected: {this.props.selected ? this.props.selected.text : "nothing"}
</div>
{this.items.map(item => {
return (
<button
key={item.id}
type="button"
onClick={() => this.props.onChange(item)}
>
{item.text}
</button>
);
})}
</div>
);
}
}
class Form extends React.Component {
state = {
firstInput: "",
selected: null
};
handleSubmit = event => {
event.preventDefault();
console.log("Form submit", this.state);
};
handleInputChange = name => event => {
this.setState({ [name]: event.target.value });
};
handleSelectedChanged = selected => {
this.setState({ selected });
};
render() {
console.log(this.state);
return (
<form onSubmit={this.handleSubmit}>
<label>This input will trigger form's onChange event</label>
<input
value={this.state.firstInput}
onChange={this.handleInputChange("firstInput")}
/>
<CustomSelect
name="kappa"
selected={this.state.selected}
onChange={this.handleSelectedChanged}
/>
</form>
);
}
}
But if you really want your way, you should pass down the handleChange function as a callback to the children and make use of this props as a function when you click on an element. Example here https://codesandbox.io/s/0o8545mn1p.
class CustomSelect extends React.Component {
items = [
{ id: 1, text: "Kappa 1" },
{ id: 2, text: "Kappa 2" },
{ id: 3, text: "Kappa 3" }
];
state = {
selected: null
};
handleSelect = item => {
this.setState({ selected: item });
this.props.onChange({ selected: item });
};
render() {
var { selected } = this.state;
return (
<div className="custom-select">
<input
name={this.props.name}
required
style={{ display: "none" }} // or type="hidden", whatever
value={selected ? selected.id : ""}
onChange={() => {}}
/>
<div>Selected: {selected ? selected.text : "nothing"}</div>
{this.items.map(item => {
return (
<button
key={item.id}
type="button"
onClick={() => this.handleSelect(item)}
>
{item.text}
</button>
);
})}
</div>
);
}
}
class Form extends React.Component {
handleChange = event => {
console.log("Form onChange");
};
render() {
return (
<form onChange={this.handleChange}>
<label>This input will trigger form's onChange event</label>
<input />
<CustomSelect name="kappa" onChange={this.handleChange} />
</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