React: programmatically change component value doesn't trigger onChange event - reactjs

I have one select field inside a React component, it value it's set through the component state an has an function attached to the onChange event. If I change the select field value manually, the onChange event it's triggered, but if I change it by changing the state value from another function it is not. It's there a way to trigger the event programmatically?
Edit:
Below is a basic example on what I need to achieve. The idea is that when the handleChange1() changes the value of state.val2 (and therefore change the option selected on the second select field) the handleChange2() is also triggered so the synthetic event is passed to the parent function (in the actual code, the select fields are another components):
class Component extends React.Component {
state = {
val1: 1,
val2: 1,
}
handleChange1 = (event) => {
const val2 = event.target.value === 3 ? 1 : null;
this.setState({
val1: event.target.value,
});
if (event.target.value === 3) {
this.setState({
val2: 1,
});
}
this.props.parentFunction(event);
}
handleChange2 = (event) => {
this.setState({
val2: event.target.value,
});
this.props.parentFunction(event);
}
render() {
return (
<div>
<select value={val1} onChange={this.handleChange1}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select value={val2} onChange={this.handleChange2}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</div>
);
}
};

Yes, there is a way! React has logic that prevents onChange from firing when an input's value is set programmatically, but it can be worked around.
Instead of:
input.value = 'foo';
Do this:
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
setter.call(input, 'foo');
input.dispatchEvent(new Event('input', { bubbles: true }));
Check out this article for the full explanation.
In case it's not clear, the value of input is the DOM element that you would get from a ref. Example:
function SomeComponent({ onChange }) {
const ref = useRef();
useEffect(() => {
setInterval(() => {
const input = ref.current;
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
setter.call(input, new Date().toISOString());
input.dispatchEvent(new Event('input', { bubbles: true }));
}, 10000);
});
return <input type="text" ref="ref" onChange={onChange} />;
}
This component would update its input with a date string & trigger the onChange callback with the new value every 10 seconds.

You should wrap your input in a dedicated component to customize the desired behavior. Something like :
class Input extends React.Component {
constructor(props){
super(props);
this.state = {
value: props.value
}
}
componentWillReceiveProps(nextProps) {
this.setState({
value: nextProps.value,
});
this.props.onChange(nextProps.value);
}
updateValue(ev) {
this.setState({
value: ev.target.value,
});
this.props.onChange(ev.target.value);
}
render() {
return (
<input
onChange={this.updateValue.bind(this)}
value={this.state.value}
{...this.props}
/>
)
}
}
and use it like:
<Input value="test" onChange={someAction} />
note that because your input is in a controlled state, value must never be null nor undefined.

I had the same problem and fortunately #gouroujo's answer works for me, but as I checked the documentation its name has changed to UNSAFE_componentWillReceiveProps() and docs say:
Note
This lifecycle was previously named componentWillReceiveProps. That name will continue to work until version 17. Use the rename-unsafe-lifecycles codemod to automatically update your components.
More details about UNSAFE_componentWillReceiveProps()
Another solution is to set a key on the component so that react can
create a new component instance rather than update the current one.
More details about Fully uncontrolled component with a key

Related

Component state not updating after unchecking checkbox in React

I simply want to change the state of my app with a handleChange method for multiple checkboxes.
My state is changed from false to true when I click each of my checkboxes, but when I un-check it, my state doesn't change to reflect it and I cant seem to figure out why!
import React, { Component } from "react";
export class StepOne extends Component {
constructor(props) {
super(props);
this.state = {
box1: false,
box2: false,
box3: false,
};
}
handleChange = (evt) => {
const box = evt.target.name;
this.setState({ [box]: !this.state.box });
};
render() {
return (
<div>
<input type="checkbox" name="box1" onChange={this.handleChange} />
<input type="checkbox" name="box2" onChange={this.handleChange} />
<input type="checkbox" name="box3" onChange={this.handleChange} />
</div>
);
}
}
When using a dot notation, compiler tries to look for a field called box in the state and since it doesn't exist - you are not getting any result when toggling the checkbox.
Use a bracket notation instead:
handleChange = (evt) => {
const box = evt.target.name;
this.setState((prevState) => ({
[box]: !prevState[box],
}));
};
Note: Consider using a function when setting the state to be sure that you refer to the relevant state.
you just have to make a change in your handleChange function as below:
handleChange = (evt) => {
const box = evt.target.name;
this.setState([box]: evt.target.checked);
};
this will give the state the same checked state of the checkbox (if the checkbox is checked, the state will be set to true, if it is not, it will be set to false).
If you have any other problem with this solution you can comment so i can help you more

react-select does not clear value when redux-form is reset

I have a stateless React function to render a react-select Select to a form. Everything else works nicely with redux-form except when redux-form is reset. I need to reset the form manually after successful post.
onChange and onBlur change the redux-form value correctly when Select has a value change. When I reset the redux-form, the redux-form value is cleared but the Select will have the old value.
function SelectInput(props) {
const { input, options, label } = props;
const { onChange, onBlur } = input;
const handleChange = ({ value }) => {
onChange(value);
};
const handleBlur = ({ value }) => {
onBlur(value);
};
return (
<FormField {...props}>
<Select placeholder={label} options={options} onChange={handleChange} onBlur={handleBlur} />
</FormField>
);
}
I converted the SelectInput to React.PureComponent, and added the value as a state inside the component and looked for when the Component received new props:
constructor(props) {
super(props);
this.state = {value: ''}
}
componentWillReceiveProps(nextProps){
this.setState({value: nextprops.input.value})
}
<Select value={this.state.value} placeholder={label} options={options} onChange={handleChange} onBlur={handleBlur} />
With this Select was not able to show the value at all.
The problem is that how I can update the Select to show empty value when redux-form that this field is part of is reset? Redux-form resets the value corretly inside the redux state and if I try to submit the form, validation notices that that Select has empty value. The Select will however display the old value so that user thinks that there is a value selected.
Reset is done by dispatching reset in the actual redux-form component. Redux devtools show that fields are reset and the redux state is cleared from all the value, Select component just won't update the DISPLAYED value to empty.
const afterSubmit = (result, dispatch) =>
dispatch(reset('datainputform'));
export default reduxForm({
form: 'datainputform',
onSubmitSuccess: afterSubmit,
})(DataInputForm);
Versions I use:
react-select#v2.0.0-beta.6
redux-form#7.3.0
You can also set a key at the form level itself. The key will take a unique value that you can store in the component state. This unique value will be updated every time reset is hit.
state = {
unique_key: ''
}
// this is the onClick handler for reset button on the form
onResetForm = () => {
reset_val = Date.now();
this.props.reset();
this.setState({unique_key: reset_val});
}
<Form actions={action_func}, key={this.state.unique_key}/>
Now whenever reset is clicked, the handler will update the unique_key. This will result in re-rendering the Form with the default values. The handler also calls the form reset function to clear the redux.
Got it working. Problem was handling the Select null value. Changed stateless function to PureComponent, added the value to state.
constructor(props) {
super(props);
this.state = { value: '' };
}
Redux-form changes the react-select value by sending new props. So added
componentWillReceiveProps(nextProps) {
if (nextProps.input.value === '') {
this.setState({ value: '' });
}
}
Added setState to handleChange:
handleChange = (data) => {
const value = data === null ? '' : data;
this.setState({ value });
this.props.input.onChange(data.value);
};
And then added the value prop.
<Select value={this.state.value}...

React-select component value not updating.

Just wondering what I am doing wrong here. When I select a value out of the list the component input field is not being filled in with the value.
class Search extends Component {
constructor(props) {
super(props);
this.state = {
name: 'order_select_field',
placeholder: "Make a selection",
}
}
componentWillMount () {
fetch('http://apirequest.com')
.then( function(response) {
return response.json();
})
.then(function(json) {
this.setState({options: json});
}.bind(this))
}
handleChange(event) {
this.setState({ })
}
render() {
return (
<div>
<Select
name={this.state.name}
options={this.state.options}
placeholder={this.state.placeholder}
/>
</div>
)
}
}
Your main issue is your handleChange method doesn't set the value
handleChange = (event) => {
this.setState({
...this.state,
value: event.target.value
})
}
With a vanilla <select> component, the onChange event would have a DOMElement reference at event.target, and react provides the value prop on the DOM element do you can use it to update your state. You're 3rd-party <Select> component might have a different event signature or expectation.
Also, since I don't know what library you're using, I've provided the state key which tracks your value as "yourSelectKey", but you can replace this with the correct key. If it's a nested property (part of an object), you may have to add the spread operator so that the other values get copied over as well.
And you need to add the onChange event handler to your select component. I recommend you follow the react docs instead of using that library.
<select
value={this.state.value}
onChange={this.handleChange}>
name={this.state.name}>
<option value="value1">Value 1</option>
<option value="value2">Value 2</option>
<option value="value3">Value 3</option>
</select>
Other issues you're facing:
You need to bind handleChange to your object instance. You can either do this in the constructor with a line this.handleChange = this.handleChange.bind(this) or by declaring handleChange as an instance variable, as I have done above.
as the commenter said, you should avoid doing the fetch call in componentWillMount, but should use componentDidMount. This is a common mistake for beginners.

How to get the latest value from selected drop down

i have this dropdonw that populates dynamically. when the user selects an item it should set the
state 'selectedValue' with the 'SelectedValue'. i have written the folllowng code but when i ran this code the alert() always display the old value not the newly selected value. why is that?
the function in react class is this
ddlProdCatsChanegeEvent: function(e) {
  
   if (this.state.isMounted)
   {
       var ele = document.getElementById('ddlCategories');
       var seleValue = ele.options[ele.selectedIndex].text;
       this.setState({selectedValue:seleValue});
       alert(this.state.selectedValue);//this always display the old selected value NOT THE new one
   }
},
the state is this:
getInitialState:function(){
   return{data1:[], data2:[], isMounted:false, selectedValue:''}
}
First of all I will recommend you to make use of refs to access the dom element instead of plain javascript. Not that its necessary but because its JSX syntax and you can use it.
Secondly, setState takes some time to mutate the state and thats the reason you are seeing the previouly selected value because it has not been changed before the alert is being triggered.
Put the alert box in the setState callback method as
this.setState({selectedValue: value}, function(){
alert(this.state.selectedValue);
});
Complete code.
var Hello = React.createClass({
getInitialState: function() {
return{ selectedValue:''}
},
handleChange: function(e) {
var value = ReactDOM.findDOMNode(this.refs.selectValue).value;
this.setState({selectedValue: value}, function(){
alert(this.state.selectedValue);
});
},
render: function() {
return (<div>
Hello {this.props.name}
<div>
<select ref="selectValue" onChange={this.handleChange}>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
</div>
</div>
)}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
JSFIDDLE
UPDATE
Take a look at this example
React highly discourages manipulating DOM directly, or using methods like getElementById(). So writing a solution w.r.t. your code would be wrong. Instead, I'm going to drop an example that's meant to help you understand how react works and how to implement what you intend.
You could always use ReactDOM, but it would be an overkill. You can instead use event.target.value to directly get the updated value from the <select /> box. How you populate the <select /> box is entirely up to you. I prefer using Array.map() to iterate over the data and return a set of <option />s.
Also note that () => {} is arrow function from es6. You can replace that with es5 function() {} anytime.
import React from 'react';
class SelectExample extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedValue: 0
}
this.updateValue = this.updateValue.bind(this);
}
updateValue(value) {
this.setState({
selectedValue: value
}, () => alert(value));
}
render() {
const dataSet = [1, 2, 3, 4];
return (
<div>
<select value={this.state.selectedValue} onChange={(e) => this.updateValue(+e.target.value)}>
<option value={0}>Default Value</option>
{
dataSet.map((item, idx) => <option value={item} key={idx}>{"Example " + item}</option>)
}
</select>
</div>
)
}
}
This code is not tested. So you may want to clear some errors, if any. You're going totally against React's use case by using document.getElementById. Please change your code to something like I have posted here.
Also read Controlled Component

React - Pass child dropdown menu state to parent

I am trying to set up a page that uses React. The page has a dropdown menu component, which should trigger an update of state in the parent. I have tried following several examples but cannot get anything to work. Here is a simplified example for both methods I've tried:
Pass a callback as a prop to the child which updates the parent state:
let Example = React.createClass({
getInitialState() {
test: "fail"
},
_updateOnChange(value) {
this.setState({test: value})
},
render() {
return (<div><DropDown onValueChange=this._updateOnChange} />
<p>{this.state.test}</p></div>);
}
});
let DropDown = React.createClass({
getInitialState() {
return { value: "fail" };
},
_onChangeHandler(e) {
this.setState({value: e.target.value});
this.props.onValueChange(this.state.value);
},
render() {
return (
<select onChange={this._onChangeHandler} value={this.state.value}>
<option value="1">1</option>
<option value="2">2</option>
</select>
);
}
});
This always displays "fail" rather than "1" or "2".
Have the parent grab the value of the dropdown menu using onChange instead of a callback.
let Example = React.createClass({
getInitialState() {
test: "fail"
},
_updateOnChange(e) {
this.setState({test: e.target.value})
},
render() {
return (<DropDown onChange=this._updateOnChange} />);
}
});
let DropDown = React.createClass({
getInitialState() {
return { value: "fail" };
},
_onChangeHandler(e) {
this.setState({value: e.target.value});
},
render() {
return (
<select onChange={this._onChangeHandler} value={this.state.value}>
<option value="1">1</option>
<option value="2">2</option>
</select>
);
}
});
I can see the state change within the DropDown component if I try to render the state, but the parent does not change. What am I doing wrong?
Your first approach should work just fine with couple of changes
The method getInitialState should return an object like this
getInitialState() {
return { test: "fail" }
}
The _onChangeHandler in the DropDown component has a problem. You have two ways to solve this
Option 1
The second line this.props.onValueChange(this.state.value); will not execute after the state has been set and hence it will display old values. setstate definition looks like this
setState(function|object nextState[, function callback])
use the callback function to make sure that the state has been set and then this.prop executes
_onChangeHandler(e) {
var self = this;
this.setState({value: e.target.value}, function(){
self.props.onValueChange(self.state.value); // <---- Makes sure that the state has been set at this stage
});
},
Option 2
instead of passing this.state.value just send e.target.value like this
_onChangeHandler(e) {
this.setState({value: e.target.value});
this.props.onValueChange(e.target.value); // <--- pass the value directly
},
Here is a demo https://jsfiddle.net/dhirajbodicherla/aqqcg1sa/4/

Resources