Reputation: 17
I'm having some problems getting the value price
from the useState products when I'm using a Form.Select
.
The idea is that with the Form.Select
you're able to select a product by its name and then onChangeProduct
will set the state of data
with the given product name and the price property of that product. I'm just not sure how to fetch the price
value without the user needing to manually selecting a price in a Form.Select
too.
The code is a simple illustration of the code I got so fare.
If you have any component that would make this easier than I'll be glad to hear about it.
Hope that makes sense.
const [data, setData] = useState([]);
const [product, setProduct] = useState([
{ name: "TV", price: 1000 },
{ name: "Phone", price: 3000 },
]);
const onChangeProduct = (name, value) => {
setData((prevValue) => {
const newValues = [...prevValues];
newValues = Object.assign({}, newValues, {[name]: value });
return newValues;
});
};
return (
<Form>
<Form.Select
onChange={(event) => {onChangeProduct("name", event.target.value);}}
value={product.name}
name="product"
>
{product.map((item) => {
return <option value={item.name}>{item.name}</option>;
})}
</Form.Select>
</Form>
);
EDIT
I've decided to go another way to make this work. I may need to set the products to an array/state as I want to be able to catch both the price and product later on.
The idea is to make the system work with add/remove product to a card where you'll be able to check the checkout with the productname and price. Therefore you're able to pick a list of products and in that way see the price property of that product.
I used the same functions I got help with here: onChange, onProductRemove and add function
CardForm.jsx -> ModalEditProducts.jsx -> ProductEdit.jsx
In CardForm.jsx
I check if there's a ID and if there's a ID I'll fetch all the products already selected by this user.
CardForm.jsx
const CardForm = () => {
const [data, setData] = useState({});
const { id } = useParams();
const [products, setProducts] = useState([]);
useEffect(() => {
if (id) {
const fetchData = async () => {
const docRef = doc(db, "user", id);
try {
const docSnap = await getDoc(docRef);
setData(docSnap.data());
} catch (error) {
console.log(error);
}
};
fetchData().catch(console.error);
}
}, []);
const handleProductChanged = (product) => {
setData((data) => ({ ...data, product }));
};
return (
<>
<Container className="mb-3 content_container_primary">
<Row>
<Col xs={12} md={12} lg={8} className="">
<Form>
<div className="box content_pa">
<Col xs={12} md={12} lg={12}>
<div>
<ModalEditProducts
onProductChanged={handleProductChanged}
data={data.product ?? []}
title="Edit products"
/>
</div>
</Col>
</div>
</Form>
</Col>
</Row>
</Container>
</>
);
};
ModalEditProducts.jsx
const ModalEditProducts = ({ title, data, onProductChanged }) => {
const [show, setShow] = useState(false);
const [newData, setNewData] = useState([]);
useEffect(() => {
setNewData(data);
}, [data, show]);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const handleProductChange = (index, name, value) => {
setNewData((prevValues) => {
const newValues = [...prevValues];
newValues[index] = Object.assign({}, newValues[index], { [name]: value });
return newValues;
});
};
const handleProductAdded = () => {
setNewData((prevValues) => [...prevValues, { product: "", price: 0 }]);
};
const handleProductRemoved = (index) => {
setNewData((prevValues) => {
const newValues = [...prevValues];
newValues.splice(index, 1);
return newValues;
});
};
const handleSubmitProducts = (e) => {
e.preventDefault();
onProductChanged(newData);
setShow(false);
};
return (
<>
<div className="content_header">
<div className="content_header_top">
<div className="header_left">Products</div>
<div className="header_right">
<Button className="round-btn" onClick={handleShow}>
<i className="fa-solid fa-pencil t-14"></i>
</Button>
</div>
</div>
</div>
{show && (
<Modal show={show} onHide={handleClose} size="">
<Modal.Header closeButton>
<Modal.Title>{title}</Modal.Title>
</Modal.Header>
<Modal.Body>
<ProductEdit
data={newData}
onProductChanged={handleProductChange}
onProductAdded={handleProductAdded}
onProductRemoved={handleProductRemoved}
/>
</Modal.Body>
<Modal.Footer>
<Form>
<Button
className="btn-skill-complete"
onClick={handleSubmitProducts}
>
Save
</Button>
</Form>
</Modal.Footer>
</Modal>
)}
</>
);
};
ProductEdit.jsx
const ProductEdit = ({ data, onProductChanged, onProductRemoved, onProductAdded }) => {
const [products, setProducts] = useState([]);
useEffect(() => {
GetProducts(setProducts);
});
return (
{data.length > 0 ? (
<Row>
<Col xs={9} md={9}>
<div className="product-modal-title mb-3">Pick a new product</div>
</Col>
<Col xs={3} md={3}>
<div className="product-modal-title mb-3 text-center">
Remove/add
</div>
</Col>
</Row>
) : null}
{data.length === 0 ? (
<Col xs={12} md={12}>
<Button
className="btn-st-large t-16 "
type="button"
onClick={onProductAdded}
>
Add a product
</Button>
</Col>
) : (
<>
{data?.map((inputField, index) => (
<div key={index}>
<Row>
<Col xs={9} md={9}>
<Form.Select
as={Col}
className="mb-3"
onChange={(event) => {
onProductChanged(index, "product", event.target.value);
}}
id="product"
name="product"
value={inputField.product}
>
<option>Choose product</option>
{products.map((item) => {
return (
<option key={item.id} value={item.name}>
{item.name} ({item.price} kr.)
</option>
);
})}
</Form.Select>
</Col>
<Col xs={3} md={3}>
<div className="btn-section">
<button
type="button"
className="round-btn"
onClick={onProductAdded}
>
<i className="fa-solid fa-plus"></i>
</button>
<button
type="button"
className="round-btn"
onClick={() => onProductRemoved(index)}
>
<i className="fa-solid fa-minus"></i>
</button>
</div>
</Col>
</Row>
</div>
))}
</>
)}
</Form>
);
};
Upvotes: 0
Views: 537
Reputation: 11532
Firstly, the products seem static and never change. No need to keep them in state therefore as there's never a need to alter them. You only need to keep something in state if it changes at some point. You can keep them outside the component and reference them. Minor performance improvement but also signals to other people looking at your code they are unchangeable and static.
Secondly the change handler is over-complex. Here you are storing not an array of objects but just 1 simple selected value (the name of the selected item). React Bootstrap is passing you the value
of the option only in the event
, and not the whole object. Since that value is just a plain string, which is considered a "primitive" in JS, you don't need to worry about any immutable state changes. Strings are copied when you pass them into a function. But then if you only have the name, how to get the price?
To get the price, you can use that selected name to lookup the relevant option and get the price property. find is a useful tool here. It lets you lookup a single item in array according to some truthiness you define.
You suggested in your question storing the price in state as well; and you might wonder why I didn't do this. Actually here, you have no choice since the library only passes you the value
of the option
. However other libraries would pass you the whole option. Nonetheless, generally speaking, storing only the bare minimum to identify the selection from the source data is better since you avoid a bunch of bugs around keeping the item in sync with the source data. It wouldn't matter here where your options data doesn't change, but it's just good practice. Better to look it up in the canonical source data using a unique identifier.
I also added a default "please select" option. The default state selects this one first (empty string)
Note price
will be undefined
before the user selects an option since the default ""
option does not appear in products
. find
returns undefined if it looks through the array and no item it loops over is truthy according to the passed condition.
This is why I used ?
(optional chaining) after the find when accessing price. You'd otherwise get an error.
const products = [
{ name: "TV", price: 1000 },
{ name: "Phone", price: 3000 },
]
const MyForm = () => {
const [selectedName, setSelectedName] = useState("");
const price = products.find(product => product.name === selectedName)?.price
return (
<Form>
<Form.Select
onChange={(event) => {setSelectedName(event.target.value);}}
value={selectedName}
>
<option value="">Select option...</option>
{products.map((item) => {
return <option value={item.name}>{item.name}</option>;
})}
</Form.Select>
</Form>
);
}
Upvotes: 1