首页 > 解决方案 > 使用 React State 对象作为另一个值的键

问题描述

在我自己尝试了一堆之后,我还没有找到一个有效的解决方案来解决我的具体问题,并且在 Stack Overflow 上查看了几个问题,所以希望有人能引导我朝着正确的方向前进。

我正在使用 React.js 钩子有一个状态对象,它根据两个选择菜单和 onChange 处理程序更改状态(大小和颜色)。在状态更改时,除了设置选择菜单之外,我还想派生一个数字 ID 来设置selectedVariant,其中 ID 来自options状态对象的不同组合,无论它们被选择的顺序如何。所有可能的组合{color: "Blue", size : "M" } -- mapped to --> 1234都是已知的,在我的控制下,可以放入映射、对象或数组等中,但我不确定设置映射和有效响应这种变化状态的最佳方法。我创建的设置和 onChange 看起来像这样,这里是一个简单的代码框,链接在这里,其中包含要演示的其他信息和下面代码的游乐场

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"
        }
      ]
    }
  ]
}

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

这会是useReducer的一个很好的用例 吗?我还研究了 javascript Map,它允许您将对象设置为键,但我无法让它在这种情况下工作。我愿意接受有关不同思考方式的建议,但希望有人能指出一些想法/资源来帮助我。

标签: javascriptreactjsdictionaryobject

解决方案


以下代码获取您的变体数据并为选择器生成数据,并且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>


推荐阅读