【问题标题】:Using a React State object as key to another value使用 React State 对象作为另一个值的键
【发布时间】:2020-10-25 20:32:31
【问题描述】:

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

我正在使用 React.js 钩子有一个状态对象,它根据两个选择菜单和 onChange 处理程序更改状态(大小和颜色)。在状态更改时,除了设置选择菜单之外,我还想派生一个数字 ID 来为 selectedVariant 设置,其中 ID 来自 options 状态对象的不同组合,无论它们的顺序如何选择。所有可能的组合,如{color: "Blue", size : "M" } -- mapped to --> 1234,在我的控制范围内都是已知的,并且可以放入映射、对象或数组等中,但我不确定设置映射和有效响应这种变化状态的最佳方法。我创建的设置和 onChange 看起来像这样,这是一个简单的代码框 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,它允许您将对象设置为键,但我无法让它在这种情况下工作。我愿意接受有关不同思考方式的建议,但希望有人能指出一些想法/资源来帮助我。

【问题讨论】:

  • 你能使用在选项改变时运行的效果吗?我不清楚你如何将{size : "M", color : "Blue"} 翻译成1234
  • @HMR So, I can definitely run an effect when options change but where I am kind of getting stuck is that even though I know {size : "M", color : "Blue"} should translate to 1234 ( and know all combinations)我怎样才能使它成为一个有效的映射?我的意思是我可以创建一个包含所有映射的数组,并在每次选项更改时进行迭代,但这对我来说似乎不是超级有效,并且仍然试图弄清楚这是否是最好/唯一的方法
  • 你能举个例子,得到1234值的翻译数据是什么样子的,你可以只用几条记录更新问题吗?
  • @HMR 当然-我更新了我的答案以反映一个示例映射,在该映射中,您可能可以通过迭代 variants 数组以找到 option_values{size : "M", color : "Blue"} 匹配的位置来导出 id 1234

标签: javascript reactjs dictionary object


【解决方案1】:

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

【讨论】:

  • 太棒了,刚刚在我的编辑器中运行了代码。感谢您的帮助...非常感谢!
  • @mcdev 更新了答案,不存在的组合不再可能。
猜你喜欢
  • 2022-01-25
  • 2016-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-08
  • 1970-01-01
  • 2021-05-30
相关资源
最近更新 更多