rex
rex

Reputation: 1023

How to render checked radio button in React

I came of with a solution for pre checked radio button based on JSON data but I think the code can be improved. Or there should be a better way to implement it. The data is structure in following way:

  {
        radioA: 'Radio A',
        radioB: 'Radio B',
        radioC: 'Radio C',
        selected: 'Radio A'
    }

Not sure if this is the best way to do it. The data structure can be modified if needed.

Based on the data provided I am rendering radio button with one of them pre-selected. Following is what I have.

var App = React.createClass({

getInitialState: function(){
    return {
        radioA: 'Radio A',
        radioB: 'Radio B',
        radioC: 'Radio C',
        selected: 'Radio A'
    }
},

render: function(){
    return(
        <div>                
            {Object.keys(this.state).map(function(key) {
                // Not displaying the selected data
                if(key !== 'selected'){

                    // Checked radio button
                    if(this.state[key] == this.state['selected']){
                        return(
                            <div>
                                <label className="radio-inline" key={key} htmlFor={key}>
                                    <input id={key} type="radio" checked value={key} /> {this.state[key]}
                                </label>
                            </div>
                        );
                    }

                    else{
                        return(
                            <div>
                                <label className="radio-inline" key={key} htmlFor={key}>
                                    <input id={key} type="radio" value={key} /> {this.state[key]}
                                </label>
                            </div>
                        );
                    }
                }
            }, this)} 
        </div>
    );
 }
 });

 React.render(<App />, document.getElementById('container'));

The code is set up following jsfiddle.com https://jsfiddle.net/rexonms/2x7ey2L5/

Is there a better solution to implement this? Thanks in advance

Upvotes: 3

Views: 13417

Answers (1)

Jordan Running
Jordan Running

Reputation: 106107

There is indeed a better way. In your render method, the JSX inside the two branches of your if statement are identical, except that one has checked and the other doesn't. Repetition like that can—and should—almost always be cleaned up.

Note: This answer was written with React 0.14 in mind. To see the code as written with more modern React idioms, see this revision.

In the case of boolean attributes like checked in JSX, you can give them a truthy or falsy value to control whether or not the rendered HTML element has the attribute or not. In other words, if you have <input checked={10 > 1}/>, you'll get <input checked/>. If you have <input checked={10 > 100}/>, you'll get <input/>.

With that in mind, we can reduce your code to something like this:

var App = React.createClass({
  getInitialState: function() {
    return {
      radios: {
        radioA: 'Radio A',
        radioB: 'Radio B',
        radioC: 'Radio C'
      },
      selected: 'radioA'
    }
  },

  renderRadioWithLabel: function(key) {
    var isChecked = key === this.state.selected;

    return (
      <label className="radio-inline" key={key} htmlFor={key}>
        <input id={key} type="radio" checked={isChecked} value={key} />
        {this.state.radios[key]}
      </label>
    );
  },
    
  render: function() {
    return (
      <div>
        {Object.keys(this.state.radios).map(function(key) {
          return this.renderRadioWithLabel(key);
        }, this)}
      </div>
    );
  }
});
 
React.render(<App />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container"></div>

As you can see, I've broken the logic that renders the radio buttons and their labels into its own function. You don't have to do that, but I find that it makes render functions with inner loops a lot easier to read, and makes your intent clearer.

Some people, myself included, would advocate breaking that logic out into its own component so you can have a stateful container component and stateless child components, which makes the whole thing much, much easier to test. Here's a good article on the topic. One more thing you might want to do is make the list of keys and labels in an array, since the order of an object's properties is not guaranteed in JavaScript. If you want to know what it might look like with those changes, take a look at this snippet:

var Radio = React.createClass({
  render: function() {
    var key = this.props.key;
    return (
      <label className="radio-inline" key={key} htmlFor={key}>
        <input id={key} type="radio" checked={this.props.selected} value={key} />
        {this.props.label}
      </label>
    );
  }
});

var RadiosContainer = React.createClass({
  getInitialState: function() {
    return { selected: this.props.initialSelected };
  },
  
  render: function() {
    return (
      <div>
        {this.props.radios.map(function(radioProps) {
          var selected = this.state.selected === radioProps.key;
          return <Radio {...radioProps} selected={selected} />;
        }, this)}
      </div>
    );
  }
});

var radios = [
  { key: 'radioA', label: 'Radio A' },
  { key: 'radioB', label: 'Radio B' },
  { key: 'radioC', label: 'Radio C' }
];
var selectedRadio = 'radioB';

React.render(
  <RadiosContainer radios={radios} initialSelected={selectedRadio} />, 
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container"></div>

Upvotes: 6

Related Questions