1

在 ReactJs 的一个小型电子商务项目(来自 WooCommerce 的采购数据)中,我有一个可变产品除了所有产品数据之外,我还有两个表示产品变体的数组对象,其中一个包含所有可用的属性和选项:

nodes: Array(3)
 0:
  id: "d2VpZ2h0OjMxOldlaWdodA=="
  name: "Weight"
  options: Array(2)
   0: "250gr"
   1: "500gr"
  position: 0
  variation: true
  visible: true
1:
 id: "cm9hc3Q6MzE6Um9hc3Q="
 name: "Roast"
  options: Array(3)
   0: "Light"
   1: "Medium"
   2: "Dark"
 position: 1
 variation: true
 visible: true
2:
 id: "cGFja2FnaW5nOjMxOlBhY2thZ2luZw=="
  name: "Packaging"
  options: Array(2)
   0: "Card"
   1: "Tin"
  position: 2
  variation: true
  visible: true

我将这个保存在一个状态并使用它来构建选择(在这种情况下为 3 个选择)然后我有一个包含所有可用组合的对象数组,包括价格、id 等:

nodes: Array(5)
 0:
  attributes:
  nodes: Array(3)
   0: {id: "NDB8fHdlaWdodHx8MjUwZ3I=", name: "weight", value: "250gr"}
   1: {id: "NDB8fHJvYXN0fHxNZWRpdW0=", name: "roast", value: "Medium"}
   2: {id: "NDB8fHBhY2thZ2luZ3x8Q2FyZA==", name: "packaging", value: "Card"}
  id: "cHJvZHVjdF92YXJpYXRpb246NDA="
  price: "€7,00"
  variationId: 40
1:
 attributes:
 nodes: Array(3)
  0: {id: "Mzh8fHdlaWdodHx8NTAwZ3I=", name: "weight", value: "500gr"}
  1: {id: "Mzh8fHJvYXN0fHxEYXJr", name: "roast", value: "Dark"}
  2: {id: "Mzh8fHBhY2thZ2luZ3x8VGlu", name: "packaging", value: "Tin"}
 length: 3
 id: "cHJvZHVjdF92YXJpYXRpb246Mzg="
 price: "€12,00"
 variationId: 38
2:
 attributes:
 nodes: Array(3)
  0: {id: "Mzd8fHdlaWdodHx8NTAwZ3I=", name: "weight", value: "500gr"}
  1: {id: "Mzd8fHJvYXN0fHxNZWRpdW0=", name: "roast", value: "Medium"}
  2: {id: "Mzd8fHBhY2thZ2luZ3x8VGlu", name: "packaging", value: "Tin"}
  length: 3
  id: "cHJvZHVjdF92YXJpYXRpb246Mzc="
  price: "€12,00"
  variationId: 37
3:
 attributes:
 nodes: Array(3)
  0: {id: "MzZ8fHdlaWdodHx8NTAwZ3I=", name: "weight", value: "500gr"}
  1: {id: "MzZ8fHJvYXN0fHxMaWdodA==", name: "roast", value: "Light"}
  2: {id: "MzZ8fHBhY2thZ2luZ3x8VGlu", name: "packaging", value: "Tin"}
 id: "cHJvZHVjdF92YXJpYXRpb246MzY="
 price: "€12,00"
 variationId: 36  
4:
attributes:
 nodes: Array(3)
  0: {id: "MzR8fHdlaWdodHx8MjUwZ3I=", name: "weight", value: "250gr"}
  1: {id: "MzR8fHJvYXN0fHxMaWdodA==", name: "roast", value: "Light"}
  2: {id: "MzR8fHBhY2thZ2luZ3x8Q2FyZA==", name: "packaging", value: "Card"}
 length: 3
 id: "cHJvZHVjdF92YXJpYXRpb246MzQ="
 price: "€7,00"
 variationId: 34

基本上,当我在任何选择上选择某些东西时,另一个选择应该根据可用的变化进行过滤:如果我选择 250 克作为重量,那么深色烤肉和锡包装应该从他们的选择中删除,因为它们不能与 250 克组合;如果我切换回 500gr(或“未选择选项”),它们应该会重新出现在与以前相同的位置。

在基本的 WooCommerce 产品页面上,它已经以这种方式工作(您可以在此处检查其行为:https : //shop.popland.it/prodotto/coffee-bean/),但在 ReactJs 中复制它比我想象的要难。目前我卡在使用空 onChange 处理程序生成选择的循环上:

const [attr, setAttr] = useState(product.attributes);
<div>
        {attr.nodes.map((attribute, l) => {
          return (
            <div key={l}>
              <span>{attribute.name}</span>
              <select
                id={attribute.name}
                onChange={handleChange}
                data-attribute_name={`attribute_${attribute.name}`}
              >
                <option>Select option</option>
                {attribute.options.map((option, o) => {
                  return (
                    <option key={o} value={option}>
                      {option}
                    </option>
                  );
                })}
              </select>
            </div>
          );
        })}
      </div>

关于如何从这里继续的任何建议/帮助?

4

1 回答 1

0

这是我的解决方案。

/src/hooks.js

import { useState } from "react";
import { filterVariations, formatVariations } from "./helpers";

export function useVariations(product) {
  const [state, setState] = useState({});
  const filteredVariations = filterVariations(
    product?.variations?.nodes,
    state,
  );
  const formatedVariations = formatVariations(
    filteredVariations,
    product?.variations?.nodes,
  );
  return [formatedVariations, state, setState];
}

/src/helpers.js

export function filterVariations(variations, state) {
  if (Object.keys(state).length === 0) {
    return variations;
  }

  return Object.keys(state)?.reduce((accStateVars, currStateVar) => {
    return accStateVars.reduce((accVars, currVar) => {
      const filteredAttrsByName = currVar?.attributes?.nodes?.filter(
        (attr) => currStateVar === attr?.name,
      );

      const withSelected = currVar?.attributes?.nodes?.findIndex(
        (attr) => attr?.attributeId === state?.[currStateVar]?.value,
      );

      return [
        ...accVars,
        {
          attributes: {
            nodes:
              withSelected >= 0
                ? currVar?.attributes?.nodes
                : filteredAttrsByName,
          },
        },
      ];
    }, []);
  }, variations);
}

export function formatVariations(filteredVariations, variations) {
  const defaultSelects = variations?.reduce(
    (accVars, currVar) => ({
      ...accVars,
      ...currVar?.attributes?.nodes?.reduce(
        (accAttrs, currAttr) => ({ ...accAttrs, [currAttr.name]: {} }),
        {},
      ),
    }),
    {},
  );

  return filteredVariations.reduce((accVars, currVar) => {
    const filteredAttrs = currVar?.attributes?.nodes?.reduce(
      (accAttrs, currAttr) => {
        const exists =
          0 <=
          accVars[currAttr.name]?.options?.findIndex(
            (option) => option.value === currAttr.attributeId,
          );
        return {
          ...accAttrs,
          [currAttr.name]: {
            placeholder: currAttr.label,
            options: exists
              ? accVars[currAttr.name]?.options || []
              : [
                  ...(accVars[currAttr.name]?.options || []),
                  { label: currAttr.value, value: currAttr.attributeId },
                ],
          },
        };
      },
      {},
    );

    return { ...accVars, ...filteredAttrs };
  }, defaultSelects);
}

/src/App.js

import "./styles.css";
import Select from "react-select";
import { useVariations } from "./hooks";

const product = {
  variations: {
    nodes: [
      {
        attributes: {
          nodes: [
            {
              name: "pa_size",
              attributeId: 254,
              id: "NTY5MHx8cGFfc2l6ZXx8MTItc3BlYWtlcg==",
              label: "Size",
              value: '12" Speaker',
            },
            {
              name: "pa_color",
              attributeId: 304,
              id: "NTY5MHx8cGFfY29sb3J8fGdyYXBoaXRl",
              label: "Color",
              value: "Graphite",
            },
            {
              name: "pa_flavor",
              attributeId: 320,
              id: "NTY5MHx8cGFfZmxhdm9yfHxnb2xk",
              label: "Flavor",
              value: "Gold",
            },
            {
              name: "pa_pallet",
              attributeId: 336,
              id: "NTY5MHx8cGFfcGFsbGV0fHxncmVlbg==",
              label: "Pallet",
              value: "Green",
            },
          ],
        },
      },
      {
        attributes: {
          nodes: [
            {
              name: "pa_size",
              attributeId: 255,
              id: "NTY4Nnx8cGFfc2l6ZXx8MTAtc3BlYWtlcg==",
              label: "Size",
              value: '10" Speaker',
            },
            {
              name: "pa_color",
              attributeId: 67,
              id: "NTY4Nnx8cGFfY29sb3J8fGJlaWdl",
              label: "Color",
              value: "Beige",
            },
            {
              name: "pa_flavor",
              attributeId: 320,
              id: "NTY4Nnx8cGFfZmxhdm9yfHxnb2xk",
              label: "Flavor",
              value: "Gold",
            },
            {
              name: "pa_pallet",
              attributeId: 337,
              id: "NTY4Nnx8cGFfcGFsbGV0fHxwZWFjaA==",
              label: "Pallet",
              value: "Peach",
            },
          ],
        },
      },
      {
        attributes: {
          nodes: [
            {
              name: "pa_size",
              attributeId: 255,
              id: "NTY4N3x8cGFfc2l6ZXx8MTAtc3BlYWtlcg==",
              label: "Size",
              value: '10" Speaker',
            },
            {
              name: "pa_color",
              attributeId: 439,
              id: "NTY4N3x8cGFfY29sb3J8fGZhdGFsLWFwcGxl",
              label: "Color",
              value: "Fatal Apple",
            },
            {
              name: "pa_flavor",
              attributeId: 319,
              id: "NTY4N3x8cGFfZmxhdm9yfHx2YW5pbGxh",
              label: "Flavor",
              value: "Vanilla",
            },
            {
              name: "pa_pallet",
              attributeId: 336,
              id: "NTY4N3x8cGFfcGFsbGV0fHxncmVlbg==",
              label: "Pallet",
              value: "Green",
            },
          ],
        },
      },
      {
        attributes: {
          nodes: [
            {
              name: "pa_size",
              attributeId: 254,
              id: "NTY4OHx8cGFfc2l6ZXx8MTItc3BlYWtlcg==",
              label: "Size",
              value: '12" Speaker',
            },
            {
              name: "pa_color",
              attributeId: 67,
              id: "NTY4OHx8cGFfY29sb3J8fGJlaWdl",
              label: "Color",
              value: "Beige",
            },
            {
              name: "pa_flavor",
              attributeId: 320,
              id: "NTY4OHx8cGFfZmxhdm9yfHxnb2xk",
              label: "Flavor",
              value: "Gold",
            },
            {
              name: "pa_pallet",
              attributeId: 337,
              id: "NTY4OHx8cGFfcGFsbGV0fHxwZWFjaA==",
              label: "Pallet",
              value: "Peach",
            },
          ],
        },
      },
      {
        attributes: {
          nodes: [
            {
              name: "pa_size",
              attributeId: 254,
              id: "NTY4OXx8cGFfc2l6ZXx8MTItc3BlYWtlcg==",
              label: "Size",
              value: '12" Speaker',
            },
            {
              name: "pa_color",
              attributeId: 436,
              id: "NTY4OXx8cGFfY29sb3J8fGJlbGdyYXZlcw==",
              label: "Color",
              value: "Belgraves",
            },
            {
              name: "pa_flavor",
              attributeId: 319,
              id: "NTY4OXx8cGFfZmxhdm9yfHx2YW5pbGxh",
              label: "Flavor",
              value: "Vanilla",
            },
            {
              name: "pa_pallet",
              attributeId: 336,
              id: "NTY4OXx8cGFfcGFsbGV0fHxncmVlbg==",
              label: "Pallet",
              value: "Green",
            },
          ],
        },
      },
    ],
  },
};

export default function App() {
  const [selects, state, setState] = useVariations(product);

  function onChange(select, value) {
    const newState = {
      ...state,
      [select]: { ...value, name: select },
    };

    if (!value) {
      delete newState[select];
    }

    setState(newState);
  }

  return (
    <div className="App">
      {Object.keys(selects).map((select) => (
        <Select
          isClearable
          key={selects[select].placeholder}
          placeholder={selects[select].placeholder}
          // value={state[select]}
          onChange={(o) => onChange(select, o)}
          options={selects[select].options}
        />
      ))}
    </div>
  );
}

操场

于 2021-07-12T11:59:00.653 回答