Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why react highlight my second <select> option while the specified value is empty?

Tags:

reactjs

I have 2 <select> as in the following code:

function FirstSelect(props) {
  const list = props.list.map(item => {
    return <option key={item.id} value={item.id}>{item.text}</option>
  })
  return <select size='8' onChange={props.onChange}>{list}</select>
}

function SecondSelect(props) {
  console.log(props.value)
  const list = props.list.map(item => {
    return <option key={item.id} value={item.id}>{item.text}</option>
  })
  return <select size='8' onChange={props.onChange} value={props.value}>{list}</select>
}

class TwoSelect extends React.Component {
  constructor() {
    super()
    this.state = {
      firstList: [
        {id: 'fitem1', text: 'fvalue1'},
        {id: 'fitem2', text: 'fvalue2'}
      ],
      secondList: [
        {id: 'sitem1', text: 'svalue1'},
        {id: 'sitem2', text: 'svalue2'}
      ],
      selectedValue1: '',
      selectedValue2: ''
    }
  }

  render() {
    return <div>
      <FirstSelect list={this.state.firstList} onChange={(e)=>{this.firstListSelectionChanged(e.target.value)}} />
      <div>selectedValue1: {this.state.selectedValue1}</div>
      <SecondSelect list={this.state.secondList} value={this.state.selectedValue2} onChange={(e)=>{this.secondListSelectionChanged(e.target.value)}} />
      <div>selectedValue2: {this.state.selectedValue2}</div>
    </div>
  }

  firstListSelectionChanged(value) {
    this.setState({
      selectedValue1: value
    })
  }

  secondListSelectionChanged(value) {
    this.setState({
      selectedValue2: value
    })
  }

}

ReactDOM.render(
  <TwoSelect />,
  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>

When it is first rendered, nothing is strange. But when I select an item in the first <select>, React highlights the first item in the second <select>, the onChange event is not fired, and selectedValue2 is still an empty string. Hence, clicking on first item of the second <select> does not fired its onChange event. (click the second item, then click the first item works normally)

What I expected is the second <select> will not be highlighted any item when its attribute value is empty!

I tried to use

this.state = {
  selectedValue1: null,
  selectedValue2: null
}

and it works like i expected, but React keeps recommending to use empty string instead of null!

What is wrong with my code?

like image 518
lazybie Avatar asked Jun 15 '26 16:06

lazybie


1 Answers

Your main problem is that it shouldn't be null, but as React suggest, it should be either undefined or an empty string.

The reason null is not an accepted value is because attributes only allow string values, and no object. As null is an object, it is not recommended to use it. Undefined however will not add the attribute (as it is not defined).

The reason why this is so can be found in the sourcecode. When a select is not a multi value, the first option gets selected by default when the value is not undefined. When a value is given, it must be stringable, as you can find in the comment section of the code here

When the value is set to undefined, react doesn't complain, as you can see in this snippet

class CustomList extends React.Component {
	render() {
    var selectedId = this.props.selectedValue;
  	return (
    	<select size="8" onChange={this.props.onChange} value={selectedId}>
      	{ this.props.items.map( item => <option key={item.id} value={item.id}>{item.text}</option> ) }
      </select>
    );
  }
}

class TwoSelect extends React.Component {
  constructor() {
    super()
    this.state = {
      firstList: [
        {id: 'fitem1', text: 'fvalue1'},
        {id: 'fitem2', text: 'fvalue2'}
      ],
      secondList: [
        {id: 'sitem1', text: 'svalue1'},
        {id: 'sitem2', text: 'svalue2'}
      ],
      selectedValue1: undefined,
      selectedValue2: undefined
    }
  }

  render() {
    return <div>
      <CustomList placeholder="select" items={this.state.firstList} selectedValue={this.state.selectedValue1} onChange={(e)=>{this.selectionChanged('selectedValue1', e.target.value)}} />
      <div>selectedValue1: {this.state.selectedValue1}</div>
      <CustomList placeholder="" items={this.state.secondList} selectedValue={this.state.selectedValue2} onChange={(e)=>{this.selectionChanged('selectedValue2', e.target.value)}} />
      <div>selectedValue2: {this.state.selectedValue2}</div>
    </div>
  }

  selectionChanged(stateKey, value) {
    this.setState({
      [stateKey]: value
    });
  }
}


ReactDOM.render(
  <TwoSelect />,
  document.getElementById('container')
);
<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="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

For some extra recommendations, there is no reason why you would make multiple list components that do the exact same thing. That would make your application hugely complex in the future, where you really don't need this complexity.

Just try to see what can be reused in your code, and then use only those single components.

For example, your FirstList and SecondList component can be simplified to for example CustomList and then you only need to call CustomList twice in your TwoSelect render method.

class CustomList extends React.Component {
  render() {
    var selectedId = this.props.selectedValue;
    return (
      <select onChange={this.props.onChange} value={selectedId}>
        { this.props.items.map( item => <option key={item.id} value={item.id}>{item.text}</option> ) }
      </select>
    );
  }
}

When creating select boxes, mostly a default value is selected, to circumvent this, you can choose to add an extra empty option, this one would be preselected in this case.

If I would now render your TwoSelect component, I can change the following things to make your application a bit easier to maintain in the future (say a third list would be added later ;-))

  render() {
    return <div>
      <CustomList 
        placeholder="select" 
        items={this.state.firstList} 
        selectedValue={this.state.selectedValue1}
        onChange={(e)=>{this.selectionChanged('selectedValue1', e.target.value)}} />
      <div>selectedValue1: {this.state.selectedValue1}</div>
      <CustomList 
        placeholder="" 
        items={this.state.secondList} 
        selectedValue={this.state.selectedValue2} 
        onChange={(e)=>{this.selectionChanged('selectedValue2', e.target.value)}} />
      <div>selectedValue2: {this.state.selectedValue2}</div>
    </div>;
  }

As you can see, I removed the need for the 2 handlers, now your selection changed event rather looks like this:

selectionChanged(stateKey, value) {
  this.setState({
    [stateKey]: value
  })
}

This would expect stateKey to be a string value, like 'selectedValue1' as you can see from the change in the handlers above

A demo using jsfiddle can be found here

like image 58
Icepickle Avatar answered Jun 22 '26 21:06

Icepickle