How to create a custom radio button and make it functional in react?

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.

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

 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.

<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.

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.

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 need to add css code. Only extra one css class is required

.show_border 
  border: 1px solid #2b2b2b !important;



Final Result



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.😀


Source link