'Selection of product options as a combination

I am trying to achieve the following for my project.

variants select

my data structure for options and combinations as following:

"options": [
{
"id": "96ce60e9-b09b-4cf6-aeca-83f75af9ef4b",
"position": 0,
"name": "Color",
"values": [
{
"id": "9056c8d4-a950-43bc-9477-283a8954285b",
"name": "",
"label": "RED"
},
{
"id": "35c7cc36-2ff4-4eb6-bc96-03cc3ea04751",
"name": "",
"label": "BLACK"
}
]
},
{
"id": "c657ee5b-57bb-4265-8113-4fefca71f785",
"position": 1,
"type": "",
"name": "SIZE",
"values": [
{
"id": "494196ec-5857-48b1-be35-ffc785e5020d",
"name": "",
"label": "LARGE"
},
{
"id": "389255cb-0c88-45e8-a1dc-6ce9fedbd98c",
"name": "",
"label": "SMALL"
}
]
}
],

Combinations:

  "combinations": [
{
"id": "84468215-d6b5-455e-bfdc-6a03cfe49d21",
"ids": [
"9056c8d4-a950-43bc-9477-283a8954285b",
"494196ec-5857-48b1-be35-ffc785e5020d"
],
"quantity": "12",
"code": 0,
"barcode": 0,
"sellPrice": 0
},
{
"id": "4c84ec32-bf4c-463d-9872-bc6a9794e7ba",
"ids": [
"9056c8d4-a950-43bc-9477-283a8954285b",
"389255cb-0c88-45e8-a1dc-6ce9fedbd98c"
],
"quantity": "12",
"code": 0,
"barcode": 0,
"sellPrice": 0
},
{
"id": "76a3ccdd-c8b4-44dc-99bf-a83a65dc0fe0",
"ids": [
"35c7cc36-2ff4-4eb6-bc96-03cc3ea04751",
"494196ec-5857-48b1-be35-ffc785e5020d"
],
"quantity": 0,
"code": 0,
"barcode": 0,
"sellPrice": 0
},
{
"id": "ec8c8cec-d2a7-4c90-8c9b-12cfeb78d0f6",
"ids": [
"35c7cc36-2ff4-4eb6-bc96-03cc3ea04751",
"389255cb-0c88-45e8-a1dc-6ce9fedbd98c"
],
"quantity": 0,
"code": 0,
"barcode": 0,
"sellPrice": 0
}
],

I've never worked on something like that before. For each combination for example (RED/SMALL), I am storing the 'RED' option id and 'Small' option id. till now I've tried the following logic, where I want to compare between ids to get the combination as object then process the order as I want:

{product.options.map((opt) => (
      <div key={opt.id}>
        <h4 className="font-bold">{opt.name}</h4>
        <ul>
          {opt.values.map((val) => (
            <li key={val.id}>
              <button
                type="button"
                className={'bg-black text-white'}
                onClick={() => {
                  //push to comboIds
                  comboIds.push(val.id);
                  console.log(comboIds);
                  //search for the combinations from comboIds
                  availableCombinations = product.combinations.filter(
                    (comb) => {
                      return comb.ids.includes(
                        comboIds.map((s) => `"${s}"`).join(', ')
                      );
                    }
                  );
                  console.log(availableCombinations);
                }}
              >
                {val.label}
              </button>
            </li>
          ))}
        </ul>
      </div>
    ))}

I appreciate any help! Thanks



Solution 1:[1]

The below snippet may be a solution to achieve the desired objective:

Code Snippet

const {useState} = React;

const SomeComponent = ({allColors, allSizes, available, ...props}) => {
  const [color, setColor] = useState('red');
  const [size, setSize] = useState('S');
  const notSelectable = {
    backgroundColor: "#DDEEFF", cursor: "default",
    borderColor: "#DDEEFF", opacity: '40%'
  };
  const getOptionsFor = (propName, propValue = null) => (
    available.filter(obj => (
      propValue === null ||
      ([propName] in obj && obj[propName] === propValue)
    ))
  );
  const isAvailable = (propName, propValue = null, prop2Name, prop2Value) => (
    getOptionsFor(propName, propValue)
    .some(obj => prop2Value && prop2Value.length > 0 && obj[prop2Name] === prop2Value)
  );
  
  return (
    <div class="flexCol">
      Colors
      <div class="boxOne">
        {allColors.map((c, i) => (
          <div
            class={"boxTwo " + c}
            style={ isAvailable('size', size, 'color', c) ? {} : {
              ...notSelectable
            }}
            key={i}
            onClick={() => {
              if (isAvailable('size', size, 'color', c)) setColor(c);
            }}
          >
            {c}
          </div>
        ))}
      </div>
      Sizes
      <div class="boxOne">
        {allSizes.map((s, i) => (
          <div
            class="boxTwo"
            style={ isAvailable('color', color, 'size', s) ? {} : {
              ...notSelectable
            }}
            key={i}
            onClick={() => {
              if (isAvailable('color', color, 'size', s)) setSize(s);
            }}
          >
            {s}
          </div>
        ))}
      </div>
      <br /><br /><br /><br />
      picked-color: {color} &emsp;
      sizes: {JSON.stringify(getOptionsFor('color', color).map(({size}) => size))}
      <br />
      picked-size: {size} &emsp;
      colors: {JSON.stringify(getOptionsFor('size', size).map(({color}) => color))}
    </div>
  );
};

const getAsArray = s => s.split(',').map(x => x.trim());
const colors = getAsArray('red, white, black, blue, magenta');
const sizes = getAsArray('XS, S, M, L, XL');
const combos = [
  { color: 'red', size: 'S'},
  { color: 'red', size: 'L'},
  { color: 'red', size: 'XS'},
  { color: 'magenta', size: 'XS'},
  { color: 'magenta', size: 'XL'},
  { color: 'magenta', size: 'S'},
  { color: 'white', size: 'M'},
  { color: 'black', size: 'M'},
  { color: 'black', size: 'L'},
  { color: 'black', size: 'XL'},
  { color: 'blue', size: 'M'},
  { color: 'blue', size: 'S'},
  { color: 'blue', size: 'XL'},
];

ReactDOM.render(
  <div>
    <b>DEMO :</b> Color+Size - Combo Selection
    <SomeComponent
      allColors={colors} allSizes={sizes}
      available={combos}
    />
  </div>,
  document.getElementById("rd")
);
.flexCol {
  display: flex;
  flex-direction: column;
}

.boxOne {
  display: flex;
  border: 2px solid black;
  width: fit-content;
  padding: 5px 15px;
  margin-top: 5px;
}

.boxTwo {
  border: 1px solid grey;
  padding: 5px;
  margin-right: 20px;
  cursor: pointer;
  border-radius: 5px;
  background-color: #FFCCAA;
}

.boxTwo:last-child { margin-right: 0; }
.red { color: red }
.blue { color: blue }
.magenta { color: magenta }
.white {
  color: darkgrey;
  border-color: black;
}
<div id="rd" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

Explanation

Have tried my best to keep the approach simple.

  • Two string arrays are sent as props from parent, namely allColors and allSizes
  • An object array named available is also sent as prop, and this has all the available color-size combinations
  • Within the child (SomeComponent), an initial state is set with color = 'red' and size = 'S'
  • The style of each choice as well as its click-ability is determined by using the method isAvailable with the prop names, values
  • This method (isAvailable) takes two pairs of prop name-values
  • For example, when checking whether a particular color (named c in the iteration) is available (ie, click-able), isAvailable is invoked with the size that has been selected as the first prop-pair.
  • It filters available to get all options where size matches the one selected by the user.
  • Then, it checks for some option in the filtered-options where the color matches the particular color (named c in the iteration)
  • When found, the particular color (c) is click-able. Else, it is disabled (different style, not clickable)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1