简体   繁体   中英

Using a React State object as key to another value

I haven't been able to figure out an efficient solution to my specific problem after trying a bunch on my own, and looking at several questions on Stack Overflow, so hopefully someone can lead me in the right direction.

I am using React.js hooks have a state object which changes state (size and color) based on two select menus and onChange handlers. On state change, in addition to setting the select menus I would like to derive an numerical ID to set for selectedVariant , in which the ID comes from different combinations of the options state object, regardless of the order in which they were selected. All of the possible combinations like {color: "Blue", size: "M" } -- mapped to --> 1234 are known, in my control, and can be put in a Map, object or array etc, but I am unsure of the best way to set mapping up and respond to this changing state efficiently. The setup and onChange I created looks like this, and here is a simple codesandbox linked here which contains additional info to demonstrate and a playground for the below code

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>
...

The current state could be {size: "M", color: "Blue"} or {color: "Gray", size: "XL"} etc. depending on what is currently selected and the order in which the select boxes were populated. However, I now need to derive a variant ID from the currently selected state to know which product variant is selected using the combination of those attributes.

Examples: {size: "M", color: "Blue"} would derive 1234 from a mapping source and then would be set as setSelectedVariant(12345) and become the new selectedVariant state.

{color: "Gray", size: "XL"} (note: different order but same keys) would derive 5678 from a mapping source and then would be set as setSelectedVariant(5678) and become the new selectedVariant state.

Update: Example Mapping: Here is a sample mapping object of what could be done to map them in order to correlate a variant to the option values.

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

which would most likely result in having to do something like a js .find() each time the state changes in order to grab the variant[n].id to pass to setSelectedVariant . Is this the best way to go about it?

Would this be a good use case for useReducer ? I also have looked into javascript Map which allows you to set an object as a key, but I wasn't able to get it working for this scenario. I am open to suggestions on different ways to think about, but hoping someone can point me to some ideas/resources to help me.

The following code takes your variants data and generates data for the selectors and a variantsByKey that can be used to look up the id of the variant based on values from the selects.

The variantsSelects can be used for the selects and contains an enabled value to prevent non existing combinations such as Blue / XL .

Here is the code, I disabled showing the console.logs but you can take the code in a project and play around with it to understand it better.

 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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM