Set radio button value in React
Asked Answered
R

4

4

I am making a simple react app with form that has radio buttons.

Here there is a default data available like,

const defaultData = [{ ContactMode: 3 }, { ContactMode: 2 }, { ContactMode: 2 }];

Requirement:

-> Need to iterate this defaultData and assign their respective ContactMode mode as checked in each row.

Working Snippet:

const { useState } = React;

const App = () => {
  const [formValue, setFormValue] = useState({
    ContactMode: 1
  });

  const defaultData = [{ ContactMode: 3 }, { ContactMode: 2 }, { ContactMode: 2 }];

  const handleInputChange = (e, index) => {
    const { name, value } = event.target;
    setFormValue({
      ...formValue,
      [name]: value
    });
  };

  const submitData = () => {
    console.log(formValue);
  };

  return (
    <div>
      <form>
        {defaultData.map((item, index) => {
          return (<React.Fragment>
            <label> Contact Mode </label>
            <input
              type="radio"
              name="ContactMode"
              checked={item.ContactMode == 1}
              value={1}
              onChange={(event) => handleInputChange(index, event)}
            />{" "}
            Email
            <input
              type="radio"
              name="ContactMode"
              checked={item.ContactMode == 2}
              value={2}
              onChange={(event) => handleInputChange(index, event)}
            />{" "}
            Text
            <input
              type="radio"
              name="ContactMode"
              checked={item.ContactMode == 3}
              value={3}
              onChange={(event) => handleInputChange(index, event)}
            />{" "}
            Both
            <br />
            <br />
            <button type="button">
               Save
             </button>
            <br />
            <br />
            <br />
          </React.Fragment>);
        })}
      </form>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Expected output:

Contact Mode  Email Text Both (Checked)

Contact Mode  Email Text(Checked) Both

Contact Mode  Email Text(Checked) Both

Note: I can't modify the name attribute name="ContactMode".. Reason is I also have individual save button each row and on click of that save button that particular row will gets saved.. And each row should have name="ContactMode" ..

Edit:

As my question is not clear in what I am trying to achieve, I am going to give the entire code here.

-> I have a stepper form in which Step 2 is employment section and by default I have the values to set into the form,

const dynamicData = [
  {
    EmploymentID: 1,
    companyName: 'Company One',
    designation: 'Designation One',
    ContactMode: 3,
  },
  {
    EmploymentID: 2,
    companyName: 'Company two',
    designation: 'Designation One',
    ContactMode: 2,
  },
];

-> Inside useEffecthook I set the form value like,

  React.useEffect(() => {
    setExpanded(0);
    setValue((prev) => {
      const companyDetails = [...dynamicData];
      return { ...prev, companyDetails };
    });
  }, []);

Here the form values with input boxes are binded properly but the radio button value doesn't gets checked.

-> There are two data's available but the first one will be expanded by default and whereas the second one will gets opened on click of the Expand button below the first one.

-> On click of the save button, that particular object (inputField) will be console logged (testing only) along with modified data..

Here the ISSUE is if we modify the data then things are working fine but the radio checked attribute alone not making the item checked.. (Checked indication is not working)..

Please go to step 2 in the below given codesandbox to see the issue.. To see the second item, click on the expand button..

Edit next-dynamic-testing-issue (forked)

Please kindly help me to set default radio button value in each row.

Rica answered 24/11, 2020 at 15:1 Comment(8)
You defined handleInputChange to consume (e, index), but in the UI you invert the order handleInputChange(index, event). Your defaultData also has two { ContactMode: 2 } and no { ContactMode: 1 }, maybe this is intentional. As @vkvkvkv points out, using the same name attribute across all radio inputs places them all in the same radio group and there can be only a single selected radio group value.Trista
Do you have control over how the inputs are mapped? Can you share a bit more about how each button saves the particular row? How is the row identified? What is the data that is saved? I have an idea but it depends on your response to these questions in order to really understand your requirements/restrictions.Trista
@DrewReese, I will edit the question soon but before that I have made a codesandbox with all the code I have here for you codesandbox.io/s/next-dynamic-testing-issue-forked-ebovz?file=/… .. In this form please go to step 2 (employment details) to see where I am placing the input boxes and radio button..Rica
@DrewReese, This is an accordion form so there will be Expand and Shrink option which makes each data expandable on click Expand ..Rica
@DrewReese, Please see the edit in question to have better understanding of your questions you mentioned in the above comment..Rica
@DrewReese, Eventhough I had lot of code, the issue is only with radio button and setting of that value..Rica
Right, reading through it now. I still don't understand why the radio groups all need the same name. Other than in dynamicData the only other references to ContactMode are the radio inputs, and each group of them should have a unique name. The save button doesn't reference anything by ContactMode.Trista
Let us continue this discussion in chat.Trista
T
1

In your sandbox I was able to get the radio buttons to work by

  1. Providing a unique name for each mapped radio group
  2. Update the change handler to uniquely handle the ContactMode property.

Update the radio groups to use the current mapped index in order to make them unique among all mapped groups.

<div className="form-group col-sm-4">
  <label className="inline-flex items-center mr-6">
    <input
      type="radio"
      className="form-radio border-black"
      name={`ContactMode-${index}`} // <-- append index to name
      value={1}
      checked={inputField.ContactMode === 1} // <-- use strict "==="
      onChange={(event) => handleInputChange(index, event)}
    />
    <span className="ml-2">Email</span>
  </label>
</div>
<div className="form-group col-sm-4">
  <label className="inline-flex items-center mr-6">
    <input
      type="radio"
      className="form-radio border-black"
      name={`ContactMode-${index}`}
      value={2}
      checked={inputField.ContactMode === 2}
      onChange={(event) => handleInputChange(index, event)}
    />
    <span className="ml-2">Text</span>
  </label>
</div>
<div className="form-group col-sm-4">
  <label className="inline-flex items-center mr-6">
    <input
      type="radio"
      className="form-radio border-black"
      name={`ContactMode-${index}`}
      value={3}
      checked={inputField.ContactMode === 3}
      onChange={(event) => handleInputChange(index, event)}
    />
    <span className="ml-2">Both</span>
  </label>
</div>

In the handleInputChange add a case to handle ContactMode specially.

const handleInputChange = (index, event) => {
  const { name, value } = event.target;
  if (name === 'designation' && !isLettersOnly(value)) {
    return;
  }

  if (name.startsWith('ContactMode')) { // <-- check name prefix
    setValue((prev) => ({
      ...prev,
      companyDetails: prev.companyDetails.map((detail, i) =>
        i === index
          ? {
              ...detail,
              ContactMode: Number(value), // <-- store back under correct key, as number for string equality check
            }
          : detail,
      ),
    }));
    return; // <-- return early so other state update is skipped!
  }

  setValue((prev) => {
    const companyDetails = prev.companyDetails.map((v, i) => {
      if (i !== index) {
        return v;
      }

      return { ...v, [name]: value };
    });

    return { ...prev, companyDetails };
  });
};

Edit set-radio-button-value-in-react

Trista answered 25/11, 2020 at 7:15 Comment(1)
Awesome solution and I have already said lot of thanks for your clear solution and will say again and again as well because your work are that much appreciated.. Thanks much Reese..Rica
A
1

I think it due to that all of the radio have the same name attribute every group should have different name. for example:

name = " ex1"
name = "ex2"
name = "ex3"

for every new group due name like this. if you do the same name it make everything messed up another think you you == instead of ===

item.ContactMode == 3

it's better to use ===

Avocation answered 24/11, 2020 at 15:10 Comment(3)
No I can't modify the name value reason is that each row have separate save button and on click of that save button, the entire row data will be gets saved.. Sometimes I get the data like ContactMode : "3", So only I have used == ..Rica
Also edited the question with individual save.. But data will come from api like const defaultData = [{ ContactMode: 3 }, { ContactMode: 2 }, { ContactMode: 2 }]; only..Rica
I think the only possible way here is to use different nameAvocation
T
1

In your sandbox I was able to get the radio buttons to work by

  1. Providing a unique name for each mapped radio group
  2. Update the change handler to uniquely handle the ContactMode property.

Update the radio groups to use the current mapped index in order to make them unique among all mapped groups.

<div className="form-group col-sm-4">
  <label className="inline-flex items-center mr-6">
    <input
      type="radio"
      className="form-radio border-black"
      name={`ContactMode-${index}`} // <-- append index to name
      value={1}
      checked={inputField.ContactMode === 1} // <-- use strict "==="
      onChange={(event) => handleInputChange(index, event)}
    />
    <span className="ml-2">Email</span>
  </label>
</div>
<div className="form-group col-sm-4">
  <label className="inline-flex items-center mr-6">
    <input
      type="radio"
      className="form-radio border-black"
      name={`ContactMode-${index}`}
      value={2}
      checked={inputField.ContactMode === 2}
      onChange={(event) => handleInputChange(index, event)}
    />
    <span className="ml-2">Text</span>
  </label>
</div>
<div className="form-group col-sm-4">
  <label className="inline-flex items-center mr-6">
    <input
      type="radio"
      className="form-radio border-black"
      name={`ContactMode-${index}`}
      value={3}
      checked={inputField.ContactMode === 3}
      onChange={(event) => handleInputChange(index, event)}
    />
    <span className="ml-2">Both</span>
  </label>
</div>

In the handleInputChange add a case to handle ContactMode specially.

const handleInputChange = (index, event) => {
  const { name, value } = event.target;
  if (name === 'designation' && !isLettersOnly(value)) {
    return;
  }

  if (name.startsWith('ContactMode')) { // <-- check name prefix
    setValue((prev) => ({
      ...prev,
      companyDetails: prev.companyDetails.map((detail, i) =>
        i === index
          ? {
              ...detail,
              ContactMode: Number(value), // <-- store back under correct key, as number for string equality check
            }
          : detail,
      ),
    }));
    return; // <-- return early so other state update is skipped!
  }

  setValue((prev) => {
    const companyDetails = prev.companyDetails.map((v, i) => {
      if (i !== index) {
        return v;
      }

      return { ...v, [name]: value };
    });

    return { ...prev, companyDetails };
  });
};

Edit set-radio-button-value-in-react

Trista answered 25/11, 2020 at 7:15 Comment(1)
Awesome solution and I have already said lot of thanks for your clear solution and will say again and again as well because your work are that much appreciated.. Thanks much Reese..Rica
S
0

@TalOrlanczyk's answer is right. You are printing 9 radio buttons in total and all have the same value in name attribute. So technically it is like having a radio group with 9 radio buttons which means that a radio group can only accept one value. I ran your code and it only selected "Text" on 3rd line.

If you are trying to have 3 separate rows (radio groups) you need to try something like this:

<input
    type="radio"
    name={"ContactMode"+index}
    checked={item.ContactMode == 1}
    value={1}
    onChange={(event) => handleInputChange(index, event)}
/>{" "}
Email
<input
    type="radio"
    name={"ContactMode"+index}
    checked={item.ContactMode == 2}
    value={2}
    onChange={(event) => handleInputChange(index, event)}
/>{" "}
Text
<input
    type="radio"
    name={"ContactMode"+index}
    checked={item.ContactMode == 3}
    value={3}
    onChange={(event) => handleInputChange(index, event)}
/>{" "}

Try it and rerun your code

Sumerian answered 24/11, 2020 at 15:49 Comment(1)
Already I updated the question saying that I cannot modify the name attribute as because I have separate save on each row so the data will be submitted in each row.. But while I receive the data, I get it in an array defaultData and I need to iterate it and assign each selected radio button values..Rica
A
0

Here are some issues in your code...

  1. Every time the component returns, the .map is called on defaultData. Hence not mapped to state.

  2. If you want the same name for each iteration, then you need to use <form> for every iteration else all the radio buttons will be grouped together, therefore only the last radio button shows as marked.

  3. It is better to use === instead of ==

I have modified your code and defaultData is used to initialize the state.

const { useState } = React;


  const defaultData = [{ ContactMode: 3 }, { ContactMode: 2 }, { ContactMode: 2 }];

const App = () => {

  const [formValue, setFormValue] = useState([...defaultData]);

  const handleInputChange = (index, e) => {
    const { name, value } = e.target;

    const newFormValue = [...formValue];
    newFormValue.splice(index,1,{[name]: value});
    
    setFormValue(newFormValue);
  };

  const submitData = () => {
    console.log(formValue);
  };

  return (
    <div>
        {formValue.map((item, index) => {
          return (<form>
            <label> Contact Mode </label>
            <input
              type="radio"
              name="ContactMode"
              checked={Number(item.ContactMode) === 1}
              value={1}
              onChange={(event) => handleInputChange(index, event)}
            />{" "}
            Email
            <input
              type="radio"
              name="ContactMode"
              checked={Number(item.ContactMode) === 2}
              value={2}
              onChange={(event) => handleInputChange(index, event)}
            />{" "}
            Text
            <input
              type="radio"
              name="ContactMode"
              checked={Number(item.ContactMode) === 3}
              value={3}
              onChange={(event) => handleInputChange(index, event)}
            />{" "}
            Both
            <br />
            <br />
          </form>);
        })}
        <button type="button" onClick={submitData}>
          {" "}
          Submit{" "}
        </button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Acapulco answered 25/11, 2020 at 6:16 Comment(1)
I have edited my question with real app problem and real app example.. Please go through it..Rica

© 2022 - 2024 — McMap. All rights reserved.