Controlled React Radio Button Doesnt Update


(Ben Glassman) #1

We are running across an issue in one of our components used for showing a group of radio buttons. The radio button elements are controlled (using checked and not defaultChecked) and they use a prop/callback to change the state in a parent component which determines which one is selected.

When you first click a radio button, it works.

The second and any subsequent clicks do not update the radio button to show as checked.

Here is our example code that exhibits the issue.

I thought it might be related to the key of each element but we are using the radio button value as key on the element which is unique.

Any help is appreciated.


(Sophie Alpert) #2

You should have checked={this.props.checked} but it doesn’t seem to work even with that change. Not sure why…


(Ben Glassman) #3

@spicyj Yes that was a typo it sounds like I should file a bug report?


(Sophie Alpert) #4

Please do.


(Ben Glassman) #5

Bug report up at https://github.com/facebook/react/issues/4930

Thanks for confirming we aren’t crazy at least.


(Michael Brown) #6

I got it working by changing the render() in your RadioButtonGroup function. It was something to do with the renderChoice method and how it was being bound (or not bound) to the class, I think.

So, I dumped that method and moved its code inside the render itself, like so:

    render() {
      var that = this;
      var choicesRendered = this.props.choices.map(function(choice, i){
            var value = choice[that.props.choiceValueKey];
            var label = choice[that.props.choiceLabelKey];
            var radioChecked = that.props.value === value;
            var key = value+(radioChecked.toString());
            console.log('renderChoice', choice);
            return (
                <RadioButton
                      name={that.props.name} 
                      label={label}
                      value={value}
                      key={key}
                      radioChecked={radioChecked} 
                      handleChange={that.handleChange}
                />
            );
      });
        return (
            <div className="form-group btn-group">
                {choicesRendered} 
            </div>
        )
    }

I didn’t manage to get it working on JSBin, but it is working for me locally, honest!


(Michael Brown) #7

Hmm… not sure what’s going on with my code displaying all on one line like that. It looks fine when I edit and preview it.


(Sophie Alpert) #8

Seems like ```JavaScript doesn’t work but ```js does. I edited your post.


(Oliver Fencott) #9

I may be a little late to this, and I’m not sure if this solves your problem as such (as it’s mostly re-written).

Forgive the half and half ES6 syntax.

const {PropTypes} = React;

const RadioButton = React.createClass({
  render: function() {
    const {name, value, checked, label} = this.props;
    
    return (
      <label>
        <input
          type='radio'
          name={name}
          value={value}
          checked={checked}
          onChange={this.handleChange}
        />
        {label}
      </label>
    );
  },
  
  handleChange: function() {
    const {value, onChange} = this.props;
    onChange(value);
  }
});

const RadioButtonGroup = React.createClass({
  propTypes: {
    name: PropTypes.string,
    checkedValue: PropTypes.string,
    choices: PropTypes.array,
    onChange: PropTypes.func,
  },
  
  getDefaultProps: function() {
    return {
      checkedValue: ''
    };
  },
  
  render: function() {
    const {choices, checkedValue, onChange} = this.props;
    
    const choiceItems = choices.map(choice => {
      const {value, label} = choice;
      const checked = value === checkedValue;
      
      return (
        <RadioButton
          key={`radio-button-${value}`}
          label={label}
          name={name}
          value={value}
          checked={checked}
          onChange={onChange}
        />
      );
    });
    
    return (
      <div>
        {choiceItems}
      </div>
    );
  }
});



const HelloWorldComponent = React.createClass({
  getInitialState: function() {
    return {
      checkedValue: ''
    };
  },
  
  render: function() {
    const {name} = this.props;
    const {checkedValue} = this.state;
    const choices = [
      {value: 'bar', label: 'Bar'},
      {value: 'baz', label: 'Baz'}
    ];
    
    return (
      <div>
        <h1>Hello {name}</h1>
        <RadioButtonGroup
          name='foo'
          checkedValue={checkedValue}
          choices={choices}
          onChange={this.handleChange}
        />
      </div>
    );
  },
  
  handleChange: function(value) {
    this.setState({
      checkedValue: value
    });
  }
});

React.render(
  <HelloWorldComponent name="Joe Schmoe"/>,
  document.getElementById('react_example')
);

(Iamcastelli) #10

Old, thread but hope this helps someone.
preventDefault() is good for hjacking button and form submission events but totally messes up the native checkbox and radio options’ state. Take it out and your code(jsbin you linked) will work like a charm.


handleChange = (e) => {
    if (!this.props.onChange) {
        return;
    }
    //e.preventDefault(); //Remove this call.
    this.props.onChange(e);
}

(bb) #11

Thank you @Sowed! That was exactly my issue.