Jon Diamond
Jon Diamond

Reputation: 210

How do I make a reusable radio button with reactJS?

I tried following the instructions here: http://react.tips/radio-buttons-in-reactjs/

This code nearly works but I can't seem to figure out what is wrong. With two radio options clicking the selected radio button changes the selection to the other option. Clicking the radio that is unselected does nothing. I've tried a bunch of things but nothing seems to get the radio to work as expected. Clicking an unselected option selects it.

Here is my code: (*Updated)

var RadioInput = React.createClass({
getInitialState: function () {
    return {selectedOption: "0"};
},
handleOptionChange: function (changeEvent) {
    this.setState({selectedOption: changeEvent.target.value});
    console.log(this.state.selectedOption, changeEvent.target, this.props.inputValue);
},    
render: function () {
  var isChecked = this.state.selectedOption === this.props.inputValue;
  return (<p><label>
          <input type="radio" name={this.props.inputName} value={this.props.inputValue}  checked={isChecked} onChange={this.handleOptionChange}/>{this.props.labelText}
    </label></p>);
}
});

My hope is that with this one generic RadioInput I can reuse it in other components that compile a bunch of form inputs together... Like I'm doing below....

var MediaInfo = React.createClass({
render: function () {
    return (
        <div className="thumbnail">
            <div className="caption text-center">
                <h3>Media Contact Options</h3>
                <form className="text-left">
                    <p>Please indicate your contact preferences for media inquiries below:</p>
                    <div className="form-group">
                        <div className="col-md-12">
                            {this.props.fields.options.map (function (data, i) {
                                return (<RadioInput key={i} inputName={data.inputName} labelText={data.labelText} inputValue={data.inputValue} />);
                            })}



                        </div>
                    </div>
                    {this.props.fields.fields.map (function (data, i) {
                        return (<TextInput key={i} inputName={data.inputName} labelText={data.labelText} placeholderText={data.placeholderText} inputValue={data.inputValue} />);
                    })}
                </form>
            </div>
        </div>            
    )
}
});
var MemberInfo = React.createClass({
getInitialState: function () {
    return {
        contactInfo: [  {inputName:"fullName", labelText:"Display Name", placeholderText:"Enter Full Name", inputValue:"some name"},
                        {inputName:"phoneNumber", labelText:"Phone Number", placeholderText:"Enter Phone Number", inputValue:"001-555-1234"},
                        {inputName:"email", labelText:"Email", placeholderText:"Enter Email", inputValue:"[email protected]"},
                        {inputName:"address", labelText:"Address", placeholderText:"Enter Address", inputValue:"123 Main St. Podunk, FL"}
                     ],
        mediaContact: {fields: [ {inputName: "email", labelText: "Media Email", placeholderText:"Enter Medial Contact Email", inputValue:"[email protected]"},
                        {inputName: "phone", labelText: "Media Phone", placeholderText:"Media Contact Phone Number", inputValue:"001-555-1234"},
                      ],
                      options: [
                          {inputName: "mediaConsent", labelText: " Yes, I would like to be contacted by the Media.", inputValue:"1"},
                          {inputName: "mediaConsent", labelText: " No, I do not wish to be contacted by the Media.", inputValue:"0"}
                      ]
                      }
    }
},
render: function () {
    return (
                    <div className="panel panel-default">
                        <div className="panel-heading">
                            <h3 className="panel-title">Manage Your Contact Info</h3> </div>
                        <div className="panel-body">
                            <div className="col-md-6">
                                <div className="row">
                                    <div className="col-xs-12">
                                        <ContactInfo fields={this.state.contactInfo} />
                                    </div>
                                </div>
                            </div>
                            <div className="col-md-6">
                                <div className="row">
                                    <div className="col-xs-12">
                                        <MediaInfo fields={this.state.mediaContact} />
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
    );

},

});

Upvotes: 2

Views: 2870

Answers (2)

Jon Diamond
Jon Diamond

Reputation: 210

I found a solution that seems to work well and I believe is a correct use of states and props (but I'm just getting the hang of them so please correct me if I am wrong).

I added the selected option value and a function for updating the state of the selected option to the parent component that maintains the state for that entire form:

var MemberInfo = React.createClass({
setSelectedOption: function (updatedValues) {
    var newSelected = this.state.mediaContact;
    newSelected.selectedOption = updatedValues;
    this.setState({ mediaContact: newSelected });
},
getInitialState: function() { return {...     
mediaContact: {..., selectedOption: "0"}}},

I passed setSelectedOption to the component responsible for rendering the form components as a prop:

<MediaInfo fields={this.state.mediaContact} setSelectedOption={this.setSelectedOption} />

I added a selected prop to my RadioInput component and calculate the value using the state property. I also pass the setSelectedOption function as a prop to each rendered RadioInput as a prop. This is probably not an ideal implementation.

{this.props.fields.options.map (function (data, i) {
                                return (<RadioInput key={i} 
                                            inputName={data.inputName} 
                                            labelText={data.labelText} 
                                            inputValue={data.inputValue} 
                                            setSelectedOption={this.props.setSelectedOption}
                                            selected={this.props.fields.selectedOption === data.inputValue}
                                            />);
                            }.bind(this))}

Now my component has no state, which it shouldn't IMHO, just an onChange handler:

var RadioInput = React.createClass({
handleOptionChange: function (changeEvent) {
    this.props.setSelectedOption(changeEvent.target.value);
},    
render: function () {
  return (<p><label>
          <input type="radio" name={this.props.inputName} value={this.props.inputValue}  checked={this.props.selected} onChange={this.handleOptionChange}/>{this.props.labelText}
    </label></p>);
}
});

Most importantly the rendered radio inputs function as expected... The default is selected and the selections change when the user clicks another option.

Below is the complete code. My goal was to create a method for creating bootstrap forms using reactJS and populating it with data from an API. I haven't written the code to save the data or pull from the API yet. My plan is to add some buttons and use the onClick handlers for the buttons to submit the data. Other than that I just need to add the componentDidMount function to populate the data from the API. Hope this helpful for someone.

var TextInput = React.createClass({
    handleChange: function (event) {
        var updatedValues = {
            inputName: this.props.inputName,
            placeholderText: this.props.placeHolderText,
            labelText:this.props.labelText,
            inputValue:event.target.value, 
        }; 
        this.props.setContactInfo(updatedValues);
    },
    render: function () {
        return (
        <div className="form-group">
            <label htmlFor={this.props.inputName}>{this.props.labelText}</label>
            <input onChange={this.handleChange} className="form-control" id={this.props.inputName} placeholder={this.props.placeholderText} value={this.props.inputValue} />
        </div> );       
    },
});

var RadioInput = React.createClass({
    handleOptionChange: function (changeEvent) {
        this.props.setSelectedOption(changeEvent.target.value);
    },    
    render: function () {
      return (<p><label>
              <input type="radio" name={this.props.inputName} value={this.props.inputValue}  checked={this.props.selected} onChange={this.handleOptionChange}/>{this.props.labelText}
        </label></p>);
    }
});

var ContactInfo = React.createClass({
    render: function () {
      return (
        <div className="thumbnail">
            <div className="caption text-center">
                <h3>Member Contact Information</h3>
                <form className="text-left" action="" method="post">
                    <input type="hidden" name="member contact info" />
                    {this.props.fields.map (function (data, i) {
                        return (<TextInput 
                                    key={i} 
                                    inputName={data.inputName} 
                                    labelText={data.labelText} 
                                    placeholderText={data.placeholderText} 
                                    inputValue={data.inputValue} 
                                    setContactInfo={this.props.setContactInfo}
                                    />);
                    }.bind(this))}
                </form>
            </div>
        </div>      
      );  
    },
});

var MediaInfo = React.createClass({
    render: function () {
        console.log(this.props.fields);
        return (
            <div className="thumbnail">
                <div className="caption text-center">
                    <h3>Media Contact Options</h3>
                    <form className="text-left">
                        <p>Please indicate your contact preferences for media inquiries below:</p>
                        <div className="form-group">
                            <div className="col-md-12">
                                {this.props.fields.options.map (function (data, i) {
                                    return (<RadioInput key={i} 
                                                inputName={data.inputName} 
                                                labelText={data.labelText} 
                                                inputValue={data.inputValue} 
                                                setSelectedOption={this.props.setSelectedOption}
                                                selected={this.props.fields.selectedOption === data.inputValue}
                                                />);
                                }.bind(this))}
                            </div>
                        </div>
                        {this.props.fields.fields.map (function (data, i) {
                            return (<TextInput 
                                        key={i} 
                                        inputName={data.inputName} 
                                        labelText={data.labelText} 
                                        placeholderText={data.placeholderText} 
                                        inputValue={data.inputValue} 
                                        setContactInfo={this.props.setContactInfo} />);
                        }.bind(this))}
                    </form>
                </div>
            </div>            
        )
    }
});

var MemberInfo = React.createClass({
    setSelectedOption: function (updatedValues) {
        var newSelected = this.state.mediaContact;
        newSelected.selectedOption = updatedValues;
        this.setState({ mediaContact: newSelected });
    },
    setContactInfo: function (updatedValues) {
        var newContactInfo = this.state.contactInfo.map(function(data, i) {if(data.inputName == updatedValues.inputName) { return updatedValues;} return data });
        var newMediaInfo = this.state.mediaContact.fields.map(function(data, i) {if(data.inputName == updatedValues.inputName) { return updatedValues;} return data });
        var holdMediaContact = this.state.mediaContact;
        holdMediaContact.fields = newMediaInfo;
        this.setState({contactInfo: newContactInfo, mediaContact: holdMediaContact});
    },
    getInitialState: function () {
        return {
            contactInfo: [  {inputName:"fullName", labelText:"Display Name", placeholderText:"Enter Full Name", inputValue:""},
                            {inputName:"phoneNumber", labelText:"Phone Number", placeholderText:"Enter Phone Number", inputValue:""},
                            {inputName:"email", labelText:"Email", placeholderText:"Enter Email", inputValue:""},
                            {inputName:"address", labelText:"Address", placeholderText:"Enter Address", inputValue:""}
                         ],
            mediaContact: {fields: [ {inputName: "email", labelText: "Media Email", placeholderText:"Enter Medial Contact Email", inputValue:""},
                                     {inputName: "phone", labelText: "Media Phone", placeholderText:"Media Contact Phone Number", inputValue:""},
                                    ],
                          options:  [ {inputName: "mediaConsent", labelText: " Yes, I would like to be contacted by the Media regarding my candidacy.", inputValue:"1"},
                                      {inputName: "mediaConsent", labelText: " No, I do not wish to be contacted by the Media.", inputValue:"0"}
                                    ],
                           selectedOption: "0"
                          }
        }
    },
    render: function () {
        return (
                        <div className="panel panel-default">
                            <div className="panel-heading">
                                <h3 className="panel-title">Manage Your Contact Info</h3> </div>
                            <div className="panel-body">
                                <div className="col-md-6">
                                    <div className="row">
                                        <div className="col-xs-12">
                                            <ContactInfo fields={this.state.contactInfo} setContactInfo={this.setContactInfo} />
                                        </div>
                                    </div>
                                </div>
                                <div className="col-md-6">
                                    <div className="row">
                                        <div className="col-xs-12">
                                            <MediaInfo fields={this.state.mediaContact} setSelectedOption={this.setSelectedOption} setContactInfo={this.setContactInfo} />
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
        );

    },
});

ReactDOM.render(<MemberInfo />, document.querySelectorAll('#member-info')[0]);

Upvotes: 1

Fabian Schultz
Fabian Schultz

Reputation: 18546

You need to set a name attribute if you want to use multiple radio buttons. Also, if there's only one radio button, you cannot uncheck it (use checkboxes for that).

var RadioInput = React.createClass({
  getInitialState: function() {
    return { selectedOption: false };
  },

  handleOptionChange: function(e) {
    this.setState({ selectedOption: e.target.value });
  },

  render: function() {
    var isChecked = this.state.selectedOption === this.props.inputValue;
    return (
      <p><label>
        <input
          type="radio"
          value={this.props.value}
          name={this.props.name}
          selected={isChecked}
          onChange={this.handleOptionChange} />
          {this.props.value}
      </label></p>
    );
  }
});

ReactDOM.render(<form>
                 <RadioInput name="group" value="one" />
                 <RadioInput name="group" value="two" />
                </form>, document.getElementById('View'));
<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="View"></div>

Upvotes: 1

Related Questions