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
Related
I have a dropdown for search via location
constructor() {
super();
this.state = {
formData:{ }
};
this.handleSelect = this.handleSelect.bind(this);
}
<div class="form-group emp-searc-location ">
<select id="emp_location" onChange={this.handleSelect} name="emp_location" value={this.state.formData.emp_location} class="form-control">
<option value="">Select Location name</option>
{this.state.emplocation.map(({ branch_location, id }, index) => (
<option value={branch_location}>{branch_location}</option>
))}
</select>
</div>
And my function
handleSelect=async(e)=>{
this.setState({
formData: {
...this.state.formData,
[e.target.name]: e.target.value,
},
});
console.log(this.state.formData);
}
And formData seems empty.but when i console console.log(e.target.value) i got correct value.but when i console console.log(this.state.formData); i got empty value.any help would be highly appreciated.
As stated in the docs, setState has an additional callback parameter: React - setState()
You can utilize the callback parameter and check the updated state there.
See also: how-to-access-updated-state-value-in-same-function-that-used-to-set-state-value
Also, may I know if the async keyword is necessary?
In your case, The value will be properly assigned to state. But If you put console.log(this.state.formData); immediately after setState. The value will not be replicated. For this, You can put console.log inside componentDidUpdate() like,
componentDidUpdate() {
console.log(this.state.formData);
}
You can check the value using the componentDidUpdate().
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.
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've got a form, it looks like this:
export default class BookingForm extends React.Component {
constructor(props) {
super(props);
this.state = {data: props.data};
}
render() {
const {booking, vehicleSelect, vehicleData, customer, drivers, fees, occasions} = this.props;
return (
<form className="grid-form">
<div className="row">
<div className="col">
<label>Is this a new or returning customer?</label>
<RadioMenu name="repeat_customer">
<RadioButton value="NEW">New Customer</RadioButton>
<RadioButton value="EXIST">Returning Customer</RadioButton>
</RadioMenu>
</div>
</div>
<div className="row new-customer-row">
<div className="col-1-2">
<label htmlFor="customer.first_name">First Name</label>
<Input id="customer.first_name" name="customer.first_name" type="text"/>
</div>
<div className="col-1-2">
<label htmlFor="customer.last_name">Last Name</label>
<Input id="customer.last_name" name="customer.last_name" type="text"/>
</div>
</div>
// .. more inputs ..
Where <RadioMenu> renders a list of <RadioButton>s which in turn contain an <Input>.
<Input> just looks like this:
export default function Input(attrs) {
return <input {...attrs}/>;
}
I made it a React component hoping I can do something useful with it.
Basically, I want all the form data to be stuffed into this.data.INPUT_NAME as soon as the input is changed. If the input name contains a . then I want to put it into a sub-object. For example, customer.last_name will be stored in this.state.data.customer.last_name. I also want to to use this.state.data to set the initial value for all the Input elements without having to explicitly add a value attribute to each of them; it should just know what value to pull out of the data object by using the input's name.
I don't know how to approach this. My first thought is that instead of returning the <form> I should put it into a variable, and then pre-process it, adding onChange and value attributes to anything of type Input, but even I try that, I don't think it would work on my RadioMenu because RadioMenu is not of type Input and I don't think I could recurse down into its children.
I could try using this context feature but the warnings are scaring me away.
I haven't looked into Flux/Reflux/Redux/xyz yet, but I don't think I really want to incorporate another framework this early in the game; I want to understand how to approach this properly before tucking it away.
So, how can I get all my form data into this.state.data?
The radio widgets look like this. I'm open to changing them if necessary. This is my first custom input widget.
// RadioMenu.jsx
import React from 'react';
import {cloneWithProps} from '../helpers/react-helpers';
import Input from './Input';
export default class RadioMenu extends Input {
constructor(props) {
super(props);
this.state = {value: props.value};
}
onChange = ev => {
this.setState({value: ev.target.value});
if(this.props.onChange) {
this.props.onChange(ev);
}
};
render() {
let {children, name, onChange, ...attrs} = this.props;
return (
<div className="radio-horizontal radio-menu" {...attrs}>
{cloneWithProps(children, btn => ({
name,
checked: btn.props.value == this.state.value,
onChange: this.onChange
}))}
</div>
);
}
}
// RadioButton.jsx
export default function RadioButton({children, ...attrs}) {
return (
<label className="checkable">
<input type="radio" {...attrs}/>
<span>{children}</span>
</label>
);
}
I was trying to use inheritance has so I could pluck out all the Inputs, regardless if they're custom or not, but I can't seem to get this to work in React. mycomp.type instanceof Input doesn't return true for sub-classes. I know React suggests composition over inheritance, but I don't know how to make that work.
This kind of problem is the reason we have libraries/patterns like Redux/Flux, but that doesn't mean it's not possible to solve without React, just a little bit harder.
In this specific case, you have a few options.
Child-Parent Events
If you change your <RadioButton /> component to accept an onChange handler, then you can listen for changes to the button and put them straight into your state.
function RadioButton(props) {
return (
// pass the onChange prop down
<input type="radio" onChange={props.onChange} />
);
}
Then update your <BookingForm /> component to make use of this new handler prop.
const setRadioState = e => this.setState({ radio: e.target.value });
// ...
<RadioMenu name="repeat_customer">
<RadioButton value="NEW" onChange={setRadioState}>New Customer</RadioButton>
<RadioButton value="EXIST" onChange={setRadioState}>Returning Customer</RadioButton>
</RadioMenu>
Accessing the Form
You can listen to the form for the submit event then iterate through the form's elements to build up an object you can put in your state.
render() {
// ...
<form onSubmit={this.onSubmit}>
// ...
},
onSubmit(e) {
const form = e.target;
const elements = form.elements;
// remove numeric keys
const keys = Object.keys(elements).filter(k => /[^\d]/.test(k);
const data = {};
keys.forEach(k => data[k] = elements[k].value);
this.setState(data);
}
If you aren't listening to the submit event and want to submit with say, a button press, then you'll need to use refs to get a instance of the form.
I'm more or less just making this approach up off the top of my head, so be wary of edge cases.
ReactLink will do what you want, but it's on its way out the door. Fortunately, it's easy to recreate this functionality in just a few lines of code.
Instead of using <input>, you can use this component:
import React from 'react';
export default class LinkedStateInput extends React.Component {
render() {
const {value, ...attrs} = this.props;
return <input {...attrs} value={value.value} onChange={ev => value.requestChange(ev.target.value)} />;
}
}
Usage example:
<LinkedStateInput value={this.linkState('passenger_count')} type="text"/>
Now just add a method to your BookingForm to handle the state updates:
linkState(name) {
return {
value: _.get(this.state.data, name, ''),
requestChange: value => {
let data = _.clone(this.state.data);
_.set(data, name, value);
this.setState({data})
}
}
}
I've used lodash here to handle deep sets/gets.
RadioMenu becomes even simpler because now it doesn't even have to remember its own state:
export default function RadioMenu({children, name, valueLink}) {
return (
<div className="radio-horizontal radio-menu">
{
valueLink
? cloneWithProps(children, btn => ({
name,
checked: btn.props.value === valueLink.value,
onChange: ev => valueLink.requestChange(ev.target.value)
}))
: cloneWithProps(children, {name})
}
</div>
);
}
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/