使用反应状态对象作为另一个值的键

时间:2020-07-05 17:06:46

标签: javascript reactjs dictionary object

我自己尝试了一堆,并查看了Stack Overflow上的几个问题后,仍无法找到解决我特定问题的有效解决方案,因此希望有人可以引导我朝正确的方向前进。

我正在使用React.js挂钩,它有一个状态对象,该对象根据两个选择菜单和onChange处理程序更改状态(大小和颜色)。关于状态更改,除了设置选择菜单外,我还想导出要为selectedVariant设置的数字ID,其中该ID来自options状态对象的不同组合,而与顺序无关在其中被选中。在我的控件中,所有可能的组合(如{color: "Blue", size : "M" } -- mapped to --> 1234都是已知的,可以放在Map,对象或数组等中,但是我不确定设置映射并有效响应此变化状态的最佳方法。我创建的setup和onChange看起来像这样,这是一个简单的codeandbox linked here,其中包含要演示的其他信息和下面的代码的游乐场

const [options, setOptions] = useState({}); // based on this state ...
const [selectedVariant, setSelectedVariant] = useState(null); // how do I derive a value for this?
...
      <select
        value={options.size}
        onChange={e => setOptions({ ...options, size: e.target.value })}
      >
        <option value="S">S</option>
        <option value="M">M</option>
        <option value="L">L</option>
        <option value="XL">XL</option>
      </select>
      <select
        value={options.color}
        onChange={e => setOptions({ ...options, color: e.target.value })}
      >
        <option value="Blue">Blue</option>
        <option value="Gray">Gray</option>
      </select>
...

当前状态可以是{size : "M", color : "Blue"}{color : "Gray", size : "XL"}等,具体取决于当前选择的内容以及选择框的填充顺序。但是,我现在需要从当前选择的状态中获取一个变体ID,以了解使用这些属性的组合选择了哪个产品变体。

示例:{size : "M", color : "Blue"}将从映射源派生1234,然后将其设置为setSelectedVariant(12345),并变成新的selectedVariant状态。

{color : "Gray", size : "XL"}(注意:顺序不同但键相同)将从映射源派生5678,然后将其设置为setSelectedVariant(5678),并变为新的selectedVariant状态。

更新示例映射: 这是一个示例映射对象,该对象可以执行哪些操作来映射它们以使变量与选项值相关。

{
  "variants": [
    {
      "id": 1234,
      "title": "M / Blue",
      "option_values": [
        {
          "name": "Size",
          "value": "M"
        },
        {
          "name": "Color",
          "value": "Blue"
        }
      ]
    },
    {
      "id": 5678,
      "title": "XL / Gray",
      "option_values": [
        {
          "name": "Size",
          "value": "XL"
        },
        {
          "name": "Color",
          "value": "Gray"
        }
      ]
    }
  ]
}

这很可能导致每次状态更改时都必须执行类似js .find()的操作,以获取variant [n] .id传递给setSelectedVariant。这是最好的方法吗?

这是useReducer 的好用例吗 ?我还研究了JavaScript Map,该JavaScript允许您将对象设置为键,但是在这种情况下我无法使其工作。我愿意就不同的思考方式提出建议,但希望有人可以向我指出一些可以帮助我的想法/资源。

1 个答案:

答案 0 :(得分:0)

以下代码获取您的变体数据,并为选择器和variantsByKey生成数据,这些数据可用于根据选择中的值查找变体的ID。

variantsSelects可用于选择,并包含一个启用的值以防止不存在的组合,例如Blue / XL

这是代码,我禁用了显示console.logs的功能,但是您可以将代码带入项目中,并对其进行处理以更好地理解它。

const data = {
  variants: [
    {
      id: 5,
      title: 'Blue',
      option_values: [
        {
          name: 'Color',
          value: 'Blue',
        },
      ],
    },
    {
      id: 1234,
      title: 'M / Blue',
      option_values: [
        {
          name: 'Size',
          value: 'M',
        },
        {
          name: 'Color',
          value: 'Blue',
        },
      ],
    },
    {
      id: 5678,
      title: 'XL / Gray',
      option_values: [
        {
          name: 'Size',
          value: 'XL',
        },
        {
          name: 'Color',
          value: 'Gray',
        },
      ],
    },
  ],
};
const removeKey = (object, key) =>
  Object.entries(object)
    .filter(([k]) => k !== key)
    .reduce((result, [key, value]) => {
      result[key] = value;
      return result;
    }, {});
//special none value
const NONE = 'NONE';
const App = ({ data }) => {
  const [
    selectedVariants,
    setSelectedVariants,
  ] = React.useState({});
  //set the values for dropdowns
  const variants = React.useMemo(
    () =>
      [
        ...data.variants
          .map(({ option_values }) => option_values)
          .flat()
          .reduce(
            (result, { name, value }) =>
              result.set(
                name,
                (result.get(name) || []).concat(value)
              ),
            new Map()
          )
          .entries(),
      ].map(([key, values]) => [key, [...new Set(values)]]),
    [data.variants]
  );
  console.log('variants:', variants);
  const variantsByKey = React.useMemo(
    () =>
      new Map(
        data.variants.map(({ id, option_values }) => [
          variants
            .map(([key]) =>
              option_values.find(({ name }) => name === key)
            )
            .filter((x) => x)
            .map(({ name, value }) => `${name}::${value}`)
            .join('++'),
          id,
        ])
      ),
    [data.variants, variants]
  );
  //selects with enabled value to disable non existant
  //  combinations
  const variantsSelects = React.useMemo(() => {
    const optionGroup = data.variants.map(
      ({ option_values }) =>
        option_values
          .map(({ name, value }) => [name, value])
          .reduce(
            (result, [key, value]) =>
              result.set(key, value),
            new Map()
          )
    );
    const selected = Object.entries(selectedVariants);
    return variants.map(([key, options]) => {
      //selected options munus current option type
      const sel = selected.filter(([k]) => k !== key);
      return [
        key,
        options.map((option) => [
          option,
          optionGroup
            .filter((variant) =>
              sel.every(
                ([key, value]) => variant.get(key) === value
              )
            )
            .some((v) => v.get(key) === option),
        ]),
      ];
    });
  }, [data.variants, selectedVariants, variants]);
  console.log('variants by key', variantsByKey);
  console.log('selects', variantsSelects);
  const [variantId, setVariantId] = React.useState();
  React.useEffect(() => {
    const variantId = variantsByKey.get(
      variants
        .map(([key]) => key)
        .filter((key) => selectedVariants[key])
        .map((key) => `${key}::${selectedVariants[key]}`)
        .join('++')
    );
    setVariantId(variantId);
  }, [selectedVariants, variants, variantsByKey]);
  const changeSelectedVariant = React.useCallback(
    (value, key) =>
      setSelectedVariants((current) =>
        value !== NONE
          ? {
              ...current,
              [key]: value,
            }
          : removeKey(current, key)
      ),
    []
  );
  return (
    <div>
      <h3>variant id: {variantId}</h3>
      {variantsSelects.map(([key, values]) => (
        <label key={key}>
          {key}
          <select
            value={selectedVariants[key]}
            onChange={(e) =>
              changeSelectedVariant(e.target.value, key)
            }
          >
            <option value={NONE}>Select option</option>
            {values.map(([value, enabled]) => (
              <option
                value={value}
                name={key}
                key={value}
                disabled={!enabled}
              >
                {value}
              </option>
            ))}
          </select>
        </label>
      ))}
    </div>
  );
};

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