RadioGroup Onchange is not firing in React

I am unable to find the root cause of why onChange event is not firing after changing the check, however, when component first loads onChange gets called.

I have prepared a CodeSandbox with some sample data.

CodeSandBox link

Here is the component ,

import React from "react";
import styled from "styled-components";
import { checklists } from "./checklists";
import { questions } from "./questions";

const RadioOption = styled.input`
  margin: 10px;
  width: 16px;
  height: 16px;
`;

const RadioGroup = (props) => {
  return React.Children.map(props.children, (child) => {
    if (child.type === RadioOption)
      return React.cloneElement(child, {
        type: "radio",
        defaultChecked: props.value === child.props.value,
        name: props.name,
        disabled: props.disabled,
        onChange: props.handleChange
      });
    return child;
  });
};

class RadioGroupDemo extends React.Component {
  state = {
    checklist: "test_checklist_checklist",
    questions: questions,
    checklists: checklists
  };
  constructor(props) {
    super(props);
    this.onRadioChange = this.onRadioChange(this);
    this.keyCount = 0;
    this.getKey = this.getKey.bind(this);
  }

  onRadioChange = (e) => {
    debugger;
    console.log("handleRadios", e);
    //let q = this.state.questions;
  };

  getKey() {
    return this.keyCount++;
  }

  //first render

  render() {
    console.log(this.state.checklists);
    return (
      <div style={{ maxHeight: "600px", overflow: "auto" }}>
        {this.state.checklist &&
          this.state.checklists.questions[this.state.checklist].map(
            (question) => {
              //debugger;
              let question_id = question.question_id;
              return (
                <div key={`${this.state.checklist}_${question.id}`}>
                  {question.title}

                  <div style={{ width: "120px", flex: "0 0 120px" }}>
                    {(() => {
                      if (
                        typeof this.state.questions[question.id] !== "undefined"
                      ) {
                        return (
                          <RadioGroup
                            name={`field_question_${question.id}`}
                            disabled={this.state.readOnly}
                            value={
                              typeof this.state.questions[question.id].answer ==
                              "undefined"
                                ? this.state.questions[question.id].answer
                                : Object.keys(this.state.questions)
                                    .reduce(
                                      (arr, key) =>
                                        arr.concat(this.state.questions[key]),
                                      []
                                    )
                                    .find((q) => {
                                      return q.question_id === question_id;
                                    }).answer
                            }
                            onChange={this.onRadioChange}
                          >
                            <RadioOption key={this.getKey()} value="yes" />
                            <RadioOption key={this.getKey()} value="no" />
                            <RadioOption key={this.getKey()} value="na" />
                          </RadioGroup>
                        );
                      }
                    })()}
                  </div>
                </div>
              );
            }
          )}
      </div>
    );
  }
}

export default RadioGroupDemo;

Please take a look at the codesandbox and tell me why the event is not firing. Thanks

Answer

Issue

You pass an onChange prop to RadioGroup but access a handleChange prop that doesn’t exist.

const RadioGroup = (props) => {
  return React.Children.map(props.children, (child) => {
    if (child.type === RadioOption)
      return React.cloneElement(child, {
        type: "radio",
        defaultChecked: props.value === child.props.value,
        name: props.name,
        disabled: props.disabled,
        onChange: props.handleChange, // <-- accessed as handleChange
      });
    return child;
  });
};

In component

<RadioGroup
  name={`field_question_${question.id}`}
  disabled={this.state.readOnly}
  value={
    typeof this.state.questions[question.id].answer ==
    "undefined"
      ? this.state.questions[question.id].answer
      : Object.keys(this.state.questions)
          .reduce(
            (arr, key) =>
              arr.concat(this.state.questions[key]),
            []
          )
          .find((q) => {
            return q.question_id === question_id;
          }).answer
  }
  onChange={this.onRadioChange} // <-- passed as onChange
>

In RadioGroupDemo you also don’t bind this to the onRadioChange handler. This binding isn’t necessary though since onRadioChange is declared as an arrow function, this is bound automatically.

constructor(props) {
  super(props);
  this.onRadioChange = this.onRadioChange(this); // <-- here
  this.keyCount = 0;
  this.getKey = this.getKey.bind(this);
}

Solution

Remove attempt to bind this to handler in constructor.

constructor(props) {
  super(props);
  this.keyCount = 0;
  this.getKey = this.getKey.bind(this);
}

Access the correct prop.

const RadioGroup = (props) => {
  return React.Children.map(props.children, (child) => {
    if (child.type === RadioOption)
      return React.cloneElement(child, {
        type: "radio",
        defaultChecked: props.value === child.props.value,
        name: props.name,
        disabled: props.disabled,
        onChange: props.onChange // <-- onChange
      });
    return child;
  });
};