abpetkov
abpetkov

Reputation: 914

React: Unselect all radio buttons via button click

I have a component that renders three custom radio buttons. The user can either submit the selected or clear (unselect) them, leaving with no selected radio buttons.

I tried some options with comparing the filterResult to the data.value, but without success. Here's a simplified code:

// imports
...

type Props = {
  filterConfig: PropTypes.object,
  filterValue: Proptypes.string,
  onFilterChange: PropTypes.func
}

class Filter extends React.Component {
  this.props = Props
  this.state = {
    filterValue: this.props.filterValue,
  }

  handleChange = (e) => {
    this.setState({ filterValue: e.target.value })
  }

  handleSubmit = () => {
    this.props.onFilterChange(this.state.filterValue)
    this.refs.filterContainer.close()
  }

  handleClear = () => {
    this.setState({ filterValue: '' })
  }

  renderOptions = () => {
    const { data, name } = this.props.filterConfig
    const options = data.map(
      (o, i) => (
        <div className='custom-radio' key={i}>
          <input
            id={`${name}-${i}`}
            name={name}
            onChange={this.handleChange}
            type='radio'
            value={o.value}
          />
          <label htmlFor={`${name}-${i}`}>
            <span />
            {o.label}
          </label>
        </div>
      )
    )

    return (
      <div>
        {options}
      </div>
    )
  }

  renderPickerNavigation = () => {
    return (
      <div>
        <a
          href='javascript:void(0)'
          onClick={this.handleClear}
        >
          Clear
        </a>
        <a
          href='javascript:void(0)'
          onClick={this.handleSubmit}
        >
          Done
        </a>
      </div>
    )
  }

  render = () => {
    return (
      <FilterWrapper
        ref='filterWrapper'
      >
        {this.renderOptions()}
        {this.renderPickerNavigation()}
      </FilterWrapper>
    )
  }
}

The data I'm passing in is:

const filters = [
  {
    data: [{
      label: 'Label 1',
      value: 1
    }, {
      label: 'Label 2',
      value: 2
    }, {
      label: 'Label 3',
      value: 3
    }],
    name: 'userFilter'
  }
]

EDIT: The click event on the native radio input works fine, so no need to change that to be on the custom radio (the span element) or the label.

Upvotes: 1

Views: 6140

Answers (2)

Chris
Chris

Reputation: 59491

You should begin with having a state variable that stores which radio is currently selected. The initial value for this should be null (or some other falsy value) if you want none to be pre-selected.

The reset button should trigger a function which resets this state variable back to the initial value.


Take a look at this simple demo, using custom css radio buttons as you requested:

class MyApp extends React.Component {
  constructor() {
    super();
    this.state = {
      selectedRadio: null,
      products: [{id: 1, name: "foo"}, {id: 2, name: "bar"}, {id: 3, name: "baz"}]
    }
  }
  
  select = (id) => {
    this.setState({selectedRadio: id});
  }
  
  reset = () => {
    this.setState({selectedRadio: null});
  }
  
  render() {
    return (
      <div>
        {this.state.products.map(
          (item) => {
            return (
              <div key={item.id}>
                <input type="radio" name="myRadio" checked={this.state.selectedRadio === item.id} />
                <label onClick={this.select.bind(this, item.id)}>{item.name}<span /></label>
              </div>
            );
          }
        )}
        <button onClick={this.reset}>Reset</button>
      </div>
    );
  }
}

ReactDOM.render(<MyApp />, document.getElementById("app"));
div {
  margin: 10px 0;
}

input[type="radio"] {
  display: none;
}

input[type="radio"]+label span {
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-left: 4px;
  vertical-align: middle;
  cursor: pointer;
  border-radius: 34%;
}

input[type="radio"]+label span {
  background-color: #333;
}

input[type="radio"]:checked+label span {
  background-color: orange;
}
<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="app"></div>

Note: Since you are hiding the input element with css, you cannot have any listeners on it (e.g onChange or onClick). Instead, you should have onClick on the span that replaces it (see code below).


For a solution of how to reset all "traditional", non-css-only radio buttons, see the snippet below:

class MyApp extends React.Component {
  constructor() {
    super();
    this.state = {
      selectedRadio: null,
      products: [{id: 1, name: "foo"}, {id: 2, name: "bar"}, {id: 3, name: "baz"}]
    }
  }
  
  select = (id) => {
    this.setState({selectedRadio: id});
  }
  
  reset = () => {
    this.setState({selectedRadio: null});
  }
  
  render() {
    return (
      <div>
        {this.state.products.map(
          (item) => {
            return (
              <div key={item.id}>
                <label>{item.name}</label>
                <input type="radio" name="myRadio" onChange={this.select.bind(this, item.id)} checked={this.state.selectedRadio === item.id} />
              </div>
            );
          }
        )}
        <button onClick={this.reset}>Reset</button>
      </div>
    );
  }
}

ReactDOM.render(<MyApp />, document.getElementById("app"));
<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="app"></div>

Upvotes: 3

Soner
Soner

Reputation: 1282

This example can help you https://codepen.io/evoyan/pen/vxGBOw

Code:

class Radio extends React.Component {
  constructor(props) {
    super(props);
    this.state = {selected: false};
  }

  toggle() {
    const {onChange} = this.context.radioGroup;
    const selected = !this.state.selected;
    this.setState({selected});
    onChange(selected, this);
  }

  setSelected(selected) {
    this.setState({selected});
  }

  render() {
    let classname = this.state.selected ? 'active' : ''
    return (
      <button type="button" className={classname} onClick={this.toggle.bind(this)}>
        {this.state.selected ? 'yes' : 'no'}
      </button>
    );
  }
}

Radio.contextTypes = {
  radioGroup: React.PropTypes.object
};

class RadioGroup extends React.Component {
  constructor(props) {
    super(props);
    this.options = [];
  }

  getChildContext() {
    const {name} = this.props;
    return {radioGroup: {
      name,
      onChange: this.onChange.bind(this)
    }};
  }

  onChange(selected, child) {
    this.options.forEach(option => {
      if (option !== child) {
        option.setSelected(!selected);
      }
    });
  }

  render() {
    let children = React.Children.map(this.props.children, child => {
      return React.cloneElement(child, {
        ref: (component => {this.options.push(component);}) 
      });
    });
    return <div className="radio-group">{children}</div>;
  }
}

RadioGroup.childContextTypes = {
  radioGroup: React.PropTypes.object
};

class Application extends React.Component {
  render() {
    return (
      <RadioGroup name="test">
        <Radio value="1" />
        <Radio value="2" />
        <Radio value="3" />
      </RadioGroup>
    );
  }
}

/*
 * Render the above component into the div#app
 */
ReactDOM.render(<Application />, document.getElementById('app'));

Upvotes: 0

Related Questions