Search code examples
javascriptreactjsreact-reduxredux-formradio-group

How to initialize standalone Radio Button Group component checked property state with redux \ redux-form


I'm learning react and I am stuck. I am trying to share a form between Edit and Create components. I am having a really hard time figuring out what I am doing wrong to initialize the values of the checked property for the radio group. After the page loads I can toggle the radio buttons. Initially, however, neither is checked.

I'm starting to think I am setting up my checked property incorrectly on the form.

Edit component (forms parent component) :

class CompanyMasterEdit extends React.Component {
  componentDidMount() {this.props.fetchCompany(this.props.match.params.companyId,this.props.match.params.companyName)}

  onSubmit = (formValues) => {    this.props.editCompany(this.props.match.params.companyId, formValues)   }
  render() {
    return (
      <div style={{ padding: 50 }}>
        <h1 style={{ textAlign: "center" }}>Edit Company Master Record</h1>
        <h5 style={{ textAlign: "center" }}>
          <span style={{ color: "red" }}>*</span> indicates required field
        </h5>
        <CompanyMasterForm
          initialValues={_.pick(
            this.props.company,
            "isKeyCompany",...
          )}
          onSubmit={this.onSubmit}
        />
const mapStateToProps = (state, ownProps) => {
  return { company: state.companies[ownProps.match.params.companyId] }
}
export default connect(mapStateToProps, { fetchCompany, editCompany })(  CompanyMasterEdit)

Form:

class CompanyMasterForm extends React.Component {
    
 <form
            className="ui form error"
            onSubmit={this.props.handleSubmit(this.onSubmit)}
          >
             <Field
              label="Is This a Key Company?"
              component={RadioGroup}
              name="isKeyCompany"
              required={true}
              options={[
                { title: "Yes", value: true },
                { title: "No", value: false },
              ]}
              checked={this.props.initialValues.isKeyCompany}
              // onClick={this.setIsKeyCompany}
              //onClick={(e) => this.setState({ isKeyCompany: e.target.checked })}
              onClick={() => {this.setState((prevState) => {return {checked: !this.props.initialValues.isKeyCompany,}})
              }}
            />
            <button className="ui button primary">Submit</button>
          </form>

RadioButtonGroup as a separate component:

    class RadioGroup extends React.Component {
    
      render() {
    
      return (
          <div className={className}>
            <div className="inline fields">
              <div className={labelClassName}>
                <label>{label}</label>
              </div>
            {options.map((o) => {
            const isChecked = o.value ? "true" : "false"
            console.log("o.value :", o.value)
            return (
              <label key={o.title}>
                <input
                  type="radio"
                  {...input}
                  value={o.value}
                  checked={isChecked === input.value}
                />
                {o.title}
              </label>
            )
          })}
            </div>
          </div>
        )
      }
    }
    // const mapStateToProps = (state) => {
    //   console.log("radio group state : ", state)
    //   return { isKeyCompany: state.form.companyMasterForm.values.isKeyCompany }
    // }
    // export default connect(mapStateToProps)(RadioGroup)
    // export default connect(null)(RadioGroup)
    export default RadioGroup

I have a codesandbox up. I feel like I'm close and dancing all around it but I can't get it. Any help is appreciated.


Solution

  • A couple of remarks:

    • Radio buttons that are mutually exclusive should have the same name prop. So we can just write a hardcoded string for one set of options.
    • The value prop doesn’t determine whether the radio button is active. This is done via checked/defaultChecked instead. The value prop is particularly useful if we have more than two options (to label each option).
    • It’s not clear what the variable input is doing in the last code example?
    • It’s always better to avoid "true" and "false" as string values, if there are only two possible values. In this case, a boolean (true or false without the quotation marks) is more suitable. This is the simplest way to represent your state, and typos such as "True" won’t slip through the cracks (when you’re using strings, such typos will go unnoticed).
    • If you’re using uncontrolled components (i.e. the value of an input is not kept in the component’s state), you should use defaultChecked instead of checked, if you want the user to be able to toggle the checkbox after the page has loaded.
    • If you want to reuse the state of the checkbox elsewhere in your app, you’ll have to use controlled components. In this case, setting the state should be done slightly differently as well (see below).

    Edit: some additional remarks in response to your comment below.

    (The code you provided contains syntax errors as-is, it’s easier to provide help if the code is correct and as minimal as possible (and nicely formatted). 😊)

    • You shouldn’t set your state onSubmit (on the form element), as you seem to be doing here. The changes should be set onChange (on the input element), because the state of the input element is the corresponding state of your app, so they must always be in sync.
    • The function that actually seems to set your state is the editCompany reducer. So you should make it available to the input element, either by passing it down from a parent component (ugly) or by connecting the input component to the Redux store (cleaner).
    class CompanyMasterEdit extends React.Component {
      componentDidMount() {
        const { fetchCompany, match } = this.props;
    
        fetchCompany(match.params.companyId, match.params.companyName);
      }
    
      render() {
        const { company } = this.props;
        return (
          <div style={{ padding: 50 }}>
            <h1 style={{ textAlign: "center" }}>Edit Company Master Record</h1>
            <h5 style={{ textAlign: "center" }}>
              <span style={{ color: "red" }}>*</span> indicates required field
            </h5>
            <CompanyMasterForm initialValues={_.pick(company, "isKeyCompany")} />
          </div>
        );
      }
    }
    const mapStateToProps = (state, ownProps) => {
      return { company: state.companies[ownProps.match.params.companyId] };
    };
    export default connect(mapStateToProps, { fetchCompany, editCompany })(
      CompanyMasterEdit
    );
    
    class CompanyMasterForm extends React.Component {
      render() {
        const { initialValues } = this.props;
        return (
          <form className="ui form error">
            <Field
              label="Is This a Key Company?"
              component={RadioGroup}
              name="isKeyCompany"
              required={true}
              options={[
                { title: "Yes", value: true },
                { title: "No", value: false },
              ]}
              checked={initialValues.isKeyCompany}
              // onClick={this.setIsKeyCompany}
              //onClick={(e) => this.setState({ isKeyCompany: e.target.checked })}
              onClick={() => {
                this.setState((prevState) => {
                  return { checked: !initialValues.isKeyCompany };
                });
              }}
            />
            <button className="ui button primary">Submit</button>
          </form>
        );
      }
    }
    
    class RadioGroup extends React.Component {
      render() {
        const { company, match } = this.props;
        return (
          <div className={className}>
            <div className="inline fields">
              <div className={labelClassName}>
                {
                  label /* note: not wrapped in `<label>...</label>`, because it’s not the description of one option, but rather of the entire set. */
                }
              </div>
              {options.map((o) => (
                <label key={o.title}>
                  <input
                    type="radio"
                    name="isKeyCompany" // for example
                    checked={company.isKeyCompany === o.value} // or passed down via props
                    onChange={() =>
                      editCompany(match.params.companyId, {
                        ...company,
                        isKeyCompany: o.value,
                      })
                    }
                  />
                  {o.title}
                </label>
              ))}
            </div>
          </div>
        );
      }
    }
    
    const mapStateToProps = (state, ownProps) => {
      return { company: state.companies[ownProps.match.params.companyId] };
    };
    export default connect(mapStateToProps, { editCompany })(RadioGroup);