React - Select Option Changes Based on Other Select

Viewed 602

I'm trying to add functionality of when i pick up a color, it shows me up only the sizes of that color and then when i choose the size it gives me only the stock available in this size

I made a state to store the value of color picked but i'm confused of how to bind it with the sizes of that color and then bind the size with the stock !

// products.js

const products = [
  {
    _id: '1',
    name: 'Men solid drop shoulder neck regular fit pullover t-shirts',
    category: 'Man',
    subcat: 'Tshirt',
    type: 'Shoulder',
    fabric: '100% Cotton',
    price: 89.99,
    variants: [
        {   
            color:'black',
            sizes:[
                {
                   size:'S',
                   stock:3
                },
                {
                   size:'L',
                   stock:12,
                }
            ]
        },

        {   
            color:'white',
            sizes:[
                {
                   size:'M',
                   stock:8
                },
                {
                   size:'L',
                   stock:2
                },
                {
                   size:'XL',
                   stock:10
                }
            ]
        }
    ],


// Product.js

import  { useState } from 'react';
import { ListGroup, Row, Col, Card,Form, Button } from 'react-bootstrap'
const Product = ({product}) => {

const [colorState, setColorState] = useState('');

    return (
        <Card className="my-3 p-3 rounded">
          <Card.Text as='div'>
              <ListGroup.Item >
                <Row>
                  <Col>
                    <Form.Control as="select" onChange={(e)=> {
                      const selectedColor = e.target.value;
                      setColorState(selectedColor);

                    }} >
                      <option value="" disabled selected hidden>Color</option>
                      {product.variants.map(variant => <option value={variant.color}>{variant.color}</option>)}
                    </Form.Control>
                    {colorState}
                  </Col>
                  <Col>
                    <Form.Control as="select" 
                     >
                      <option value="" disabled selected hidden>Size</option>
                     {product.variants[0].sizes.map(sizes => <option value={sizes.size}>{sizes.size}</option>)} 
                    </Form.Control>

                    Quantity: <input type="number" name="points"  min='1' max={product.variants[0].sizes[0].stock}/>
                  </Col>

                </Row>
              </ListGroup.Item>
            </div>
          </Card.Text>
          <Button variant="primary">Purchase</Button>{' '}
        </Card>


    )
}

export default Product




3 Answers

you can update Product Component to look like this


const Product = ({product}) => {

const [colorState, setColorState] = useState('');
    return (
      <Card className="my-3 p-3 rounded">
          <Card.Text as='div'>
              <ListGroup.Item >
                <Row>
                  <Col>
                    <Form.Control as="select" onChange={(e)=> {
                      const selectedColor = e.target.value;
                      setColorState(selectedColor);

                    }} >
                      <option value="" disabled selected hidden>Color</option>
                      {product.variants.map(variant => <option value={variant.color}>{variant.color}</option>)}
                    </Form.Control>
                    {colorState}
                  </Col>
                  <Col>
                    <Form.Control as="select">
                      <option value="" disabled selected hidden>Size</option>
                      {product.variants.map(variant=> {
                        if(variant["color"] === colorState){
                          return variant.sizes.map(sizes => <option value={sizes.size}>{sizes.size}</option>)
                        }
                        return ""
                      })}
                    </Form.Control>
                    Quantity: <input type="number" name="points"  min='1' max={product.variants[0].sizes[0].stock}/>
                  </Col>

                </Row>
              </ListGroup.Item>
          </Card.Text>
          <Button variant="primary">Purchase</Button>{' '}
        </Card>
      )
}

export default Product

Similar to Mohamed's answer but with the max quantity also added:

  const products = [
    {
      _id: "1",
      name: "Men solid drop shoulder neck regular fit pullover t-shirts",
      category: "Man",
      subcat: "Tshirt",
      type: "Shoulder",
      fabric: "100% Cotton",
      price: 89.99,
      variants: [
        {
          color: "black",
          sizes: [
            {
              size: "S",
              stock: 3
            },
            {
              size: "L",
              stock: 12
            }
          ]
        },

        {
          color: "white",
          sizes: [
            {
              size: "M",
              stock: 8
            },
            {
              size: "L",
              stock: 2
            },
            {
              size: "XL",
              stock: 10
            }
          ]
        }
      ]
    }
  ];
  //----------------------------------------------------
  const product = products[0];
  const [colorState, setColorState] = useState("");
  const [selectedSize, setSelectedSize] = useState("");
  return (
    <div className="App">
      <Card className="my-3 p-3 rounded">
        <Card.Text as="div">
          <ListGroup.Item>
            <Row>
              <Col>
                <Form.Control
                  as="select"
                  onChange={(e) => {
                    const selectedColor = e.target.value;
                    setColorState(selectedColor);
                  }}
                >
                  <option value="" disabled selected hidden>
                    Color
                  </option>
                  {product.variants.map((variant) => (
                    <option value={variant.color}>{variant.color}</option>
                  ))}
                </Form.Control>
                {colorState}
              </Col>
              <Col>
                <Form.Control
                  as="select"
                  onChange={(e) => {
                    setSelectedSize(e.target.value);
                  }}
                >
                  <option value="" disabled selected hidden>
                    Size
                  </option>
                  {product.variants.map((variant) => {
                    if (variant["color"] === colorState) {
                      return variant.sizes.map((sizes) => (
                        <option value={sizes.size}>{sizes.size}</option>
                      ));
                    }
                    return "";
                  })}
                </Form.Control>
                Quantity:
                {selectedSize && (
                  <input
                    type="number"
                    name="points"
                    min="1"
                    max={
                      product.variants[0].sizes.filter(
                        (size) => size.size === selectedSize
                      )[0].stock
                    }
                  />
                )}
              </Col>
            </Row>
          </ListGroup.Item>
        </Card.Text>
        <Button variant="primary">Purchase</Button>{" "}
      </Card>
    </div>
  );
}

I would do this in a little different way by converting all of the input fields such as Color, Size and Quantity into controlled components. As, it gives me better control to update/reset the particular Quantity/Size whenever the Color is changed and also, to update/reset Quantity when the Size alone is changed.

Also, I would store the selected variant of the product object rather than just storing color itself and the same case for size, instead of just storing the size, I would store size object which is selected through which I will be able to control the quantity.

After all of those changes this is how my Product.js would look like.

// Product.js

import { useState } from "react";
import { ListGroup, Row, Col, Card, Form, Button } from "react-bootstrap";

const Product = ({ product }) => {
  const defaultVariantObj = { color: "", sizes: [] };
  const [selectedVariant, setSelectedVariant] = useState(defaultVariantObj);
  const defaultSizeObj = { size: "", stock: 0 };
  const [selectedSize, setSelectedSize] = useState(defaultSizeObj);
  const defaultQuantity = 0;
  const [selectedQuantity, setSelectedQuantity] = useState(defaultQuantity);

  return (
    <Card className="my-3 p-3 rounded">
      <Card.Text as="div">
        <ListGroup.Item>
          <Row>
            <Col>
              <Form.Control
                as="select"
                onChange={(e) => {
                  const {
                    target: { value }
                  } = e;
                  setSelectedVariant(
                    product.variants.find(({ color }) => color === value) || {}
                  );
                  setSelectedSize(defaultSizeObj);
                  setSelectedQuantity(defaultQuantity);
                }}
                value={selectedVariant.color}
              >
                <option value="" disabled selected hidden>
                  Color
                </option>
                {product.variants.map((variant) => (
                  <option value={variant.color}>{variant.color}</option>
                ))}
              </Form.Control>
              {selectedVariant.color}
            </Col>
            <Col>
              <Form.Control
                as="select"
                onChange={(e) => {
                  const {
                    target: { value }
                  } = e;
                  setSelectedSize(
                    selectedVariant?.sizes.find(({ size }) => size === value) ||
                      {}
                  );
                  setSelectedQuantity(defaultQuantity);
                }}
                value={selectedSize.size}
              >
                <option value="" disabled selected hidden>
                  Size
                </option>
                {selectedVariant?.sizes?.map((sizeObj) => (
                  <option key={sizeObj.size} value={sizeObj.size}>
                    {sizeObj.size}
                  </option>
                ))}
              </Form.Control>
              Quantity:{" "}
              <input
                type="number"
                name="points"
                min={!selectedSize?.stock ? 0 : 1}
                max={selectedSize?.stock || 0}
                value={selectedQuantity}
                onChange={(e) => {
                  const {
                    target: { value }
                  } = e;
                  setSelectedQuantity(value);
                }}
              />
            </Col>
          </Row>
        </ListGroup.Item>
      </Card.Text>
      <Button variant="primary">Purchase</Button>{" "}
    </Card>
  );
};

export default Product;

Here is the Link to the Sandbox. For simplicity in the sandbox I have used just two products, so as to deal with multiple products if any.

Related