How to create a custom radio button and make it functional in react?
Tags: react, radiobutton, functionalcomponent, tailwindcss
Published: 2021-10-27 19:08:47

I was creating a product page for one of my e-commerce projects. And then I came into an issue of building a color and size selection component. I searched a lot on developer's best friend "Google" but could not find any optimal solution. After few exploration and testing I came up with this code.
##Basic Setup

1.React
2.Tailwind

##Preparing the data
Let's say we have an array of sizes for a particular product fetched from the db. We need to convert it to an object containing an id which will be used to map input to label and the value.
```javascript
const productSize = ["S", "M", "X", "XXL"];
//map
const sizeData = productSize?.map((item) => ({
  id: `input_${item}`,
  value: item,
}));
```
##Defining States
I defined a state for tracking the state of radio values. If you are using multiple radio buttons , you should create multiple states
```javascript
 let [sizeValue, setSizeValue] = useState("");
```
##Rendering the component
Map through the sizeData array and render each size. I am using tailwind css for styling which might make the code a bit messy.
Radio input is set to invisible so that the label is the clickable element. input id is mapped to label for.
```javascript
<div className="main max-w-lg mx-auto">
      <div className="grid grid-cols-4 gap-8 gap-y-4">
        {sizeData?.map(({ id, value }) => (
          <div className="-mt-2" key={id}>
            <input
              id={id}
              className="invisible radio_custom"
              type="radio"
              value={value}
              checked={sizeValue === { sizeValue }}
              onChange={(e) => setSizeValue(e.target.value)}
            />
            <label htmlFor={id} className="radio_custom_label">
              <div
                className={`border border-gray-300 py-3 text-center cursor-pointer "
                        }`}
              >
                {value}
              </div>
            </label>
          </div>
        ))}
      </div>
    </div>
```
##Issues
Now, I have few issues with the above code. When a button is clicked , the other clicked buttons stays active. This doesn't give the toggle functionality that we require. The state is changing but the border on "onClick" persists. So even if the the state is changing, border stays on the element.
What we need is to remove the border from all element and add border to the "target" element.
```javascript
onChange={(e) => {
    const nodes = e.target.parentElement.parentElement.childNodes;
    for (let i = 0; i < nodes.length; i++) {
    nodes[i].lastChild.firstChild.classList.remove("show_border");
    }
    e.target.nextSibling.firstChild.classList.toggle("show_border");
    return setSizeValue(e.target.value);
}}

```
##Final Code
I had to refactored the code like three times🙂. Here is the final code.
```javascript
import React, { useState } from "react";

const Main = () => {
  const productSize = ["S", "M", "X", "XXL"];
  //map
  const sizeData = productSize?.map((item) => ({
    id: `input_${item}`,
    value: item,
  }));
  let [sizeValue, setSizeValue] = useState("");
  console.log(sizeValue);
  return (
    <div className="main max-w-lg mx-auto">
      <div className="grid grid-cols-4 gap-8 gap-y-4">
        {sizeData?.map(({ id, value }) => (
          <div className="-mt-2" key={id}>
            <input
              id={id}
              className="invisible radio_custom"
              type="radio"
              value={value}
              checked={sizeValue === { sizeValue }}
              onChange={(e) => {
                const nodes = e.target.parentElement.parentElement.childNodes;
                for (let i = 0; i < nodes.length; i++) {
                  nodes[i].lastChild.firstChild.classList.remove("show_border");
                }
                e.target.nextSibling.firstChild.classList.toggle("show_border");
                return setSizeValue(e.target.value);
              }}
            />
            <label htmlFor={id} className="radio_custom_label">
              <div
                className={`border border-gray-300 py-3 text-center cursor-pointer "
                        }`}
              >
                {value}
              </div>
            </label>
          </div>
        ))}
      </div>
    </div>
  );
};

export default Main;
```

I used tailwind so no need to add css code. Only extra one css class is required
```css
.show_border {
  border: 1px solid #2b2b2b !important;
}
```
##Final Result
<img width="100%" style="width:100%" src="https://media.giphy.com/media/4mDxFTVDZXd2pz3sLq/giphy.gif">

##Final Notes
There are multiple ways of doing this. If you have any suggestion or code improvement please do share. Feel free to connect. It's lively to make new friends.😀