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/
Related
I'm trying to use the select tag in React. I have the following component:
class InteractionHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
allDays: [],
selected: ""
};
this.dateChanged = this.dateChanged.bind(this);
}
componentDidMount() {
this.setState({
allDays: ["a", "b"],
selected: "a"
});
}
dateChanged(e) {
e.preventDefault();
console.log(event.target.value);
}
}
And in my render, I have the following:
render() {
return (
<div>
<select value={this.state.selected} onChange={this.dateChanged}>
{this.state.allDays.map((x) => {
return <option key={`${x}`} value={`${x}`}>{x}</option>
})};
</select>
</div>
);
}
However, when I select a different option, my console prints undefined instead of the option I selected. What is going on and how do I prevent it?
You are calling it wrong
dateChanged(e) {
console.log(e.target.value);
}
You should use prevent default in case where you donot want your page to take a refresh and try to make a server side call. In case of select there is nothing like this.
You have a typo in dateChanged(e) method. You need to log e.target.value instead of event.target.value.
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
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
I have radio button inputs with an onChange callback that triggers a long action - a computation on the server. I want my input to update now to appear checked as soon as it is clicked, not only when the computation is finished - otherwise the user thinks he has not clicked right and keeps clicking.
So I cannot set the "checked" attribute with props, otherwise it has to wait for the end of the computation. Calling myinput.checked = "checked" in the inChange callback does not update it either.
My workaround is to write 10 ugly and stupid lines of code like theses ones so that a state updates first, and then we get the props at the end of the action:
getInitialState: function () {
// "anti-pattern", according to the docs
return {value: this.props.value};
},
componentWillReceiveProps: function(nextProps) {
// because we use the state in the <input>, it needs to mirror the props
// when they change for another reason. I call it dirty.
this.setState({value: nextProps.value});
},
onChange: function (e) {
this.setState({value: e.target.value}); // to update it right now
Actions.longAction(e.target.value); // long action
},
render: function() {
...
return <input
...
type="radio"
checked={ this.state.value === 1}
/>;
}
Is there any better way to check right away, visually, the option that the user clicked ?
In React, don't read DOM.
Instead, manage checked in you Checkbox component. Since checkbox only holds boolean value (checked), it's super easy. On every change, you invert this value and call onChange handler.
See this structure, feel free to edit it to your needs.
var Parent = React.createClass({
getInitialState() {
return {
checked: false,
}
},
onChange(checked) {
this.setState({ checked });
},
render: function() {
return <Checkbox checked={this.state.checked} onChange={this.onChange} />;
}
});
var Checkbox = React.createClass({
onChange() {
this.props.onChange(!this.props.checked);
},
render() {
const props = {
...this.props,
onChange: this.onChange,
};
return <input type="checkbox" {...props} />;
}
});
Working fiddle: https://jsfiddle.net/69z2wepo/37640/
So I've read that we should try avoiding refs when accessing Child components
(React refs with components)
(https://facebook.github.io/react/docs/more-about-refs.html)
In my case, however, I couldn't think of a way to avoid this in my situation..
Scenario:
I have a MyForm parent component which contains a TagInput child component, which resembles Stackoveflow's "Tags" input field. As the user types an input+SPACE, a tag is added to TagInput's internal state. When the user submits the form, the selected tag list is posted to my server.
Implementation:
var MyForm = React.createClass({
submit: function() {
var selectedTags = this.refs.tagInput.state.selectedTags;
$.post(
SERVER_URL,
data: { tags: selectedTags }
);
},
render: function() {
return (
<form onSubmit={this.submit}>
<TagInput ref="tagInput">
</form>
);
}
});
var TagInput = React.createClass({
getInitialState: function() {
return {
selectedTags: []
}
},
// This is called when the user types SPACE in input field
handleAddTag: function(tag) {
this.setState({
selectedTags: this.state.selectedTags.concat(tag);
});
},
render: function() {
return (
<form>
<ul>{this.state.selectedTags}</ul>
<input type="text" />
</form>
);
}
});
The above code works fine and does what is expected. The only concern is I'm using refs to directly access the Child component's internal state, and I'm not sure if this is the right "React" way.
I guess one option is to maintain the "selectedTags" state in MyForm instead of TagInput. This doesn't really make sense in my case, because in reality my form contains 5 TagInput components and many other states to manage..
Can anyone think of a way to improve my design? Or is using refs unavoidable in my case?
Thanks
You can pass handleAddTag from the parent component into the child and keep the state of the selected tags in the form component. Now when you submit the form you are pulling from the state on the form rather than using refs.
var MyForm = React.createClass({
getInitialState: function() {
return {
selectedTags: []
}
},
submit: function() {
var selectedTags = this.state.selectedTags;
$.post(
SERVER_URL,
data: { tags: selectedTags }
);
},
// This is called when the user types SPACE in input field
handleAddTag: function(tag) {
this.setState({
selectedTags: this.state.selectedTags.concat(tag);
});
},
render: function() {
return (
<form onSubmit={this.submit}>
<TagInput handleAddTag={this.handleAddTag} ref="tagInput">
</form>
);
}
});
//Use this.props.handleAddTag() to update state in Form Component
var TagInput = React.createClass({
render: function() {
return (
<form>
<ul>{this.state.selectedTags}</ul>
<input type="text" />
</form>
);
}
});
I found that using refs to get some piece of data is not as bad as using refs to modify the UI which is very bad as it will cause the UI to not be inline with your state or your application.