I have a container which is a child container. This child container handles states and it also receives some props from the parent. I have two select statements which onChange sets state in the child container. For some reason, the setState() is causing the container to re render. The weird part is that the render() and also my setState() code is called only once. (I added debugger to check). Please find my Select combobox code below:
<Select
native
name="evidenceNode"
value={nodeType}
onChange={this.handleChange('nodeType')}
className={classes.textField}
>
<option />
{NODE_TYPE.map(function (item, i) {
return <option key={i} value={item}>{item}</option>
})}
</Select>
<Select
native
name="modelType"
value={modelType}
onChange={this.handleChange('modelType')}
className={classes.textField}
>
<option />
{MODEL_TYPE.map(function (item, i) {
return <option key={i} value={item}>{item}</option>
})}
</Select>
Here is my handleChange function:
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
I am suspecting that this is a very small fix but I don't know where am I going wrong.
Things I have tried:
Change the way I am calling handle change to a arrow function and it didnt work
I removed one of the Select statement and tried running again and it worked.
I tried to remove the handleChange call from one of the Select statement and it worked fine.
Also I should mention: I have a componentWillReceiveProps function (Not sure if it matters)
componentWillReceiveProps(nextProps, nextContext) {
if(nextProps.selectedNode !== this.state.selectedNode){
this.setState({
selectedNode: nextProps.selectedNode
});
}
}
The issue is onChange={this.handleChange('modelType')}.
You're not attaching an event, you're calling a method with that.
You're entering in an infinite loop because of that.
this.handleChange('modelType') occurs a re-render which call again this.handleChange('modelType')...etc
Attach on the onChange event an anonymous function which call handleChange
onChange={e => this.handleChange('modelType', e)}
And change handleChange params declaration :
handleChange = (name, event) => {}
The problem wasn't with the handleChange listener. Apparently [Material-UI]https://material-ui.com/ (The tool I am using for the form elements) required us to add a FormControl element above every Select I add. So the component should look something like this.
<FormControl
// className={classes.formControl}
>
<Select
native
name="nodeType"
value={nodeType}
onChange={this.handleChange('nodeType')}
className={classes.textField}
inputProps={{
name: 'nodeType',
id: 'nodeType'
}}
>
<option/>
{NODE_TYPE.map(function (item, i) {
return <option key={i} value={item}>{item}</option>
})}
</Select>
</FormControl>
The mistake I made was I had one FormControl and it had two Select elements inside it. This particular thing isn't documented properly on their website.
I think Material-UI recursively calls handleChange on every Select component inside the Form control and since I had more than one, it was going into a loop. I hope this helps.
Related
I want to get option's value and key when select onChange.
I know I can get option's value simplicity used event.target.value in onChangeOption function, but how can I get key?
<select onChange={this.onChangeOption} value={country}>
{countryOptions.map((item, key) =>
<option key={key} value={item.value}>
{item.name}
</option>
)}
</select>
You will need to pass value in key as a different prop.
From docs:
Keys serve as a hint to React but they don’t get passed to your components. If you need the same value in your component, pass it explicitly as a prop with a different name:
Read: https://reactjs.org/docs/lists-and-keys.html
Passing key as a prop works obviously, but much quicker way could be to include the key as a custom attribute in your html.
class App extends React.Component {
constructor(props) {
super(props);
this.onSelect = this.onSelect.bind(this);
}
onSelect(event) {
const selectedIndex = event.target.options.selectedIndex;
console.log(event.target.options[selectedIndex].getAttribute('data-key'));
}
render() {
return (
<div>
<select onChange = {this.onSelect}>
<option key="1" data-key="1">One</option>
<option key="2" data-key="2">Two</option> </select>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
A simple way of doing this is:
const functionCall = (event) => {
console.log(event.target.getAttribute('a-key'));
}
<button a-key={1} onClick={functionCall}>Press Me</button>
I put onClick but you can use any event.
You can pass anything through value. I think this solves your problem.
<select onChange={this.handleChange}>
{this.state.countryOptions.map((item, key) =>
<option key={key}
value={JSON.stringify({key, value:item.value})}>
//passing index along with value
{item.value}
</option>
)}
</select>
handleChange(e) {
let obj = JSON.parse(e.target.value)
// gives object{ key:country index, value: country value}
}
Imagine a component like this:
<Component data={[{id:'a23fjeZ', name="foo"}, ...]}`
Which renders a list of inputs, and gets in a data prop which is a collection (Array of Objects):
function Component ( props ){
// cached handler per unique key. A self-invoked function with a "cache" object
const onChange = (cache => param => {
if( !cache[param] )
cache[param] = e =>
console.log(param, e.target.name, e.target.value)
return cache[param];
}
)({});
return props.data.map(item =>
<input key={item.id} name={item.name} onChange={onChange(item.id)} />
}
}
As you can see, the key isn't being accessed, but is passed into a currying function which is handling the caching internally, to prevent "endless" creation of functions on re-renders.
I'm a beginner and have battle with this for days now!
Now, I will be happy to share you my final answer!
This is really easy -All you got to do is to declare an anonymous function! for an onClick on the item you wanna detect its key!
e.g
data.map((item) => (<div
className = "newItem" key = {item.id} onClick = {() = displayKey(item.id)}
>{item.value}</div>))
then your function can be thus;
will display any "val" passed in
const displayKey = (val) => {
console.log(val)
}
I hope I was able to help!
battling with a problem can be a pain in the ass!
Kind Regards .
So you do need to pass the key into the <option> as a prop, but since the <select> is a native field and handles changes internally it gets a little hard to get that key value back out.
Lets start one issue at a time, passing the prop into the option could be as simple as wrapping the option component like so and using the index prop as the new key prop.
const CustomOption = ({ value, children, index }) => (
<option key={index} value={value}>{value}</option>
);
Also note that we're creating a custom component wrapper above to swallow the index prop from being applied to <option /> itself as react doesnt like unknown props on dom elements.
Now if we could handle the selected event inside the option we would be done, but we can't do that. so we need to make a custom select as well:
class CustomSelect extends React.Component {
static propTypes = {
value: PropTypes.object,
onChange: PropTypes.func,
}
handleChanged = (e) => {
const newValue = e.target.value;
let newKey;
// iterate through our children searching for the <CustomOption /> that was just selected
React.children.forEach(this.children, (c) => {
if (c.props && c.props.index && c.props.value === newValue) {
newKey = c.props.key;
}
});
this.props.onChange(e, newKey);
}
render() {
return (
<select value={this.props.value} onChange={this.handleChanged}>
{this.props.children}
</select>
);
}
}
it has two props value, and onChange. Notice that we intercept the change event in order to find the index (the key) and we pass it along to the parent as the second parameter. This is not very nice but I can't think of another easy way to do this while still using the native <select> element.
Note you need to replace your usages of <select> and <optoin> to use these new classes, and assign the index prop along with the key prop on the option.
First of all, I apologize for my bad English.I am using the select component of the UI Kitten. Every item fails when I change it.
This is the mistake;
"Warning cannot update during an existing state react native"
I am sharing sample codes with you.
const data = [
"test 1",
"test 2"
];
constructor(props) {
super(props);
this.state = {
selectedIndex: new IndexPath(0),
displayValue: null
};
}
componentDidMount() {
this._handleSetSelectedIndex = this._handleSetSelectedIndex.bind(this);
}
_handleSetSelectedIndex(value) {
this.setState({selectedIndex: value});
}
in Render function;
<Select
style={{ width: 300 }}
placeholder='Default'
value={data[this.state.selectedIndex.row]}
selectedIndex={this.state.selectedIndex}
onSelect={index => this._handleSetSelectedIndex(index)}>
{data.map((key, value) => {
return (
<SelectItem key={value} title={key}/>
);
})}
</Select>
The problem is that the program can't update state inside rendering..it goes through infinite loop so "selectedIndex" must have an event handler function to handle when to setState it.
onSelect={index => this._handleSetSelectedIndex(index)}>
this line seems problematic to me.
try onSelect={this._handleSetSelectedIndex}
also add a console.log inside this function and see if it ever fires when you select a new item.
Also worth to try to reproduce the basic onSelect first following their tutorial,
<Select
selectedIndex={selectedIndex}
onSelect={index => setSelectedIndex(index)}>
<SelectItem title='Option 1'/>
<SelectItem title='Option 2'/>
<SelectItem title='Option 3'/>
</Select>
When you call setSelectedIndex, you pass the index, but it is the event object. Use this syntaxis:
onChange={(event) => this._handleSetSelectedIndex(event)}>
See full example in the playground: https://jscomplete.com/playground/s524798
I have a stateful component that holds some state
state={
name:'',
age:'',
occupation:''
}
And a function to update the state onChange listener
onValueChange = (key, event) => {
this.setState({ [key]: event.target.value });
};
I pass the state and function down to child as props
<ComponentA {...this.state} changed={this.onValueChange}>
Inside my component B which is a child of A I want to programatically create inputs based on given props and change the state by invoking that function every time user types in input.
<ComponentB>
{ Object.entries(this.props)
.filter(
prop =>
prop[0] !== 'changed'
)
.map(propName => (
<Input
key={propName}
label={propName[0]}
value={propName[1]}
onValueChange={this.props.changed(propName[0])}
/>
))}
</ComponentB>
My Input component just renders the following
<input
onChange={this.props.onValueChange}
value={this.props.value}
type={this.props.type}
placeholder=" "
/>
Can't make it work for some reason.
Thanks for any help!
You are currently invoking this.props.changed straight away on render by writing onValueChange={this.props.changed(propName[0])}. Instead of invoking it on render you should give it a function to call when onValueChange occurs instead.
You also want to give the Input a unique key prop that will not change between state updates, so that React doesn't create an entirely new component every time and you e.g. lose focus of the input. You can use propName[0] instead, which will be unique.
<Input
key={propName[0]}
label={propName[0]}
value={propName[1]}
onValueChange={event => this.props.changed(propName[0], event)}
/>
I want to get option's value and key when select onChange.
I know I can get option's value simplicity used event.target.value in onChangeOption function, but how can I get key?
<select onChange={this.onChangeOption} value={country}>
{countryOptions.map((item, key) =>
<option key={key} value={item.value}>
{item.name}
</option>
)}
</select>
You will need to pass value in key as a different prop.
From docs:
Keys serve as a hint to React but they don’t get passed to your components. If you need the same value in your component, pass it explicitly as a prop with a different name:
Read: https://reactjs.org/docs/lists-and-keys.html
Passing key as a prop works obviously, but much quicker way could be to include the key as a custom attribute in your html.
class App extends React.Component {
constructor(props) {
super(props);
this.onSelect = this.onSelect.bind(this);
}
onSelect(event) {
const selectedIndex = event.target.options.selectedIndex;
console.log(event.target.options[selectedIndex].getAttribute('data-key'));
}
render() {
return (
<div>
<select onChange = {this.onSelect}>
<option key="1" data-key="1">One</option>
<option key="2" data-key="2">Two</option> </select>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
A simple way of doing this is:
const functionCall = (event) => {
console.log(event.target.getAttribute('a-key'));
}
<button a-key={1} onClick={functionCall}>Press Me</button>
I put onClick but you can use any event.
You can pass anything through value. I think this solves your problem.
<select onChange={this.handleChange}>
{this.state.countryOptions.map((item, key) =>
<option key={key}
value={JSON.stringify({key, value:item.value})}>
//passing index along with value
{item.value}
</option>
)}
</select>
handleChange(e) {
let obj = JSON.parse(e.target.value)
// gives object{ key:country index, value: country value}
}
Imagine a component like this:
<Component data={[{id:'a23fjeZ', name="foo"}, ...]}`
Which renders a list of inputs, and gets in a data prop which is a collection (Array of Objects):
function Component ( props ){
// cached handler per unique key. A self-invoked function with a "cache" object
const onChange = (cache => param => {
if( !cache[param] )
cache[param] = e =>
console.log(param, e.target.name, e.target.value)
return cache[param];
}
)({});
return props.data.map(item =>
<input key={item.id} name={item.name} onChange={onChange(item.id)} />
}
}
As you can see, the key isn't being accessed, but is passed into a currying function which is handling the caching internally, to prevent "endless" creation of functions on re-renders.
I'm a beginner and have battle with this for days now!
Now, I will be happy to share you my final answer!
This is really easy -All you got to do is to declare an anonymous function! for an onClick on the item you wanna detect its key!
e.g
data.map((item) => (<div
className = "newItem" key = {item.id} onClick = {() = displayKey(item.id)}
>{item.value}</div>))
then your function can be thus;
will display any "val" passed in
const displayKey = (val) => {
console.log(val)
}
I hope I was able to help!
battling with a problem can be a pain in the ass!
Kind Regards .
So you do need to pass the key into the <option> as a prop, but since the <select> is a native field and handles changes internally it gets a little hard to get that key value back out.
Lets start one issue at a time, passing the prop into the option could be as simple as wrapping the option component like so and using the index prop as the new key prop.
const CustomOption = ({ value, children, index }) => (
<option key={index} value={value}>{value}</option>
);
Also note that we're creating a custom component wrapper above to swallow the index prop from being applied to <option /> itself as react doesnt like unknown props on dom elements.
Now if we could handle the selected event inside the option we would be done, but we can't do that. so we need to make a custom select as well:
class CustomSelect extends React.Component {
static propTypes = {
value: PropTypes.object,
onChange: PropTypes.func,
}
handleChanged = (e) => {
const newValue = e.target.value;
let newKey;
// iterate through our children searching for the <CustomOption /> that was just selected
React.children.forEach(this.children, (c) => {
if (c.props && c.props.index && c.props.value === newValue) {
newKey = c.props.key;
}
});
this.props.onChange(e, newKey);
}
render() {
return (
<select value={this.props.value} onChange={this.handleChanged}>
{this.props.children}
</select>
);
}
}
it has two props value, and onChange. Notice that we intercept the change event in order to find the index (the key) and we pass it along to the parent as the second parameter. This is not very nice but I can't think of another easy way to do this while still using the native <select> element.
Note you need to replace your usages of <select> and <optoin> to use these new classes, and assign the index prop along with the key prop on the option.
I have a FormControl which is a Select that I am using in a form. It works fine when adding a new resource, but when I want to edit an existing record I need to set the value of the control. I'm not seeing a good way to set the value of the select / combobox. This is what my JSX looks like.
<FormGroup controlId="group">
<ControlLabel>Group</ControlLabel>
<FormControl componentClass="select" placeholder="Group"
inputRef={ref => { this.groupSelect = ref; }}
onChange={this.groupSelect}>
<option></option>
{
this.state.groups.map(function (group) {
return <option key={group.id} value={group.id}>{group.name}</option>
})
}
</FormControl>
</FormGroup>
I've tried to access the component this way but it comes up undefined.
console.debug(this.groupSelect);
this.groupSelect.value(1);
UPDATE:
I'm trying the following, but this doesn't appear to be working either because I don't have access in the state, calling bind causes an error.
<FormGroup controlId="group">
<ControlLabel>Group</ControlLabel>
<FormControl componentClass="select" placeholder="Group"
inputRef={(ref) => { this.state.groupSelect = ref }}
onChange={this.groupSelect}>
<option></option>
{
this.state.groups.map(function (group) {
return <option key={group.id} value={group.id} selected={this.state.selectedGroupId == group.id}>{group.name}</option>
}).bind(this)
}
</FormControl>
</FormGroup>
You are missing a crucial piece here, which is you need to set the value of the component. Add:
value={this.state.groupSelectValue || defaultValue}
Where groupSelectValue is the value from the record you're editing, and the defaultValue is what you want to default to if there is no record or no value in the record.
Then, in the onChange event function (groupSelect(e)), you will do:
this.setState({
groupSelectValue: e.target.value
});
So that the state is updated with the selection.
The selected value should come from model (state or property), you are trying to achieve what you need with 'jquery way '- which is wrong.