Reputation: 349
I'm trying to create a switcher with react that when I click it it change the price of another component and when I click again over it return the original price.
My first approach was this:
I crated the input with the type of a checkbox so when checked
is true
change the price and when is false
return the original price, some kind of toggle all handled with the funcion handleDiscount
<input
checked={toggle}
onChange={handleDiscount}
type="checkbox"
className="switch-input"
/>
I created the handleDiscount
function that change the toggle
from his initial state which is false
to true
and and after that a ternary operator that check the condition to set the price.
const handleDiscount = () => {
setToggle(!toggle);
toggle === true ? setPrice(10) : setPrice(20);
};
the problem is that when is click over the checkbox again the price don't change.
I have to work with the useEffect
hook? which it's the best approach for this kind of work?
for example I wrote the same code with my knowledge in vanilaJS and work, here is the example:
const switcher = document.querySelector("input");
switcher.addEventListener("click", () => {
const priceBasic = document.querySelector(".priceBasic");
const pricePro = document.querySelector(".pricePro");
const priceMaster = document.querySelector(".priceMaster");
if (switcher.checked == true) {
priceBasic.innerHTML = `<h1>$49.99</h1>`;
pricePro.innerHTML = "$69.99";
priceMaster.innerHTML = "$89.99";
} else {
priceBasic.innerHTML = "$19.99";
pricePro.innerHTML = "$24.99";
priceMaster.innerHTML = "$39.99";
}
});
Upvotes: 2
Views: 857
Reputation: 4741
@codemonkey's answer is correct but if you want something a little simpler:
function YourComponent() {
const [price, setPrice] = useState(20);
const [toggle, setToggle] = useState(false);
const handleDiscount = () => {
const newValue = !toggle;
setToggle(newValue);
setPrice(newValue ? 10 : 20);
}
// ...
}
Or if price
is only computed based on toggle
:
function YourComponent() {
const [toggle, setToggle] = useState(false);
const price = useMemo(
() => toggle ? 10 : 20,
[toggle]
);
const handleDiscount = () => {
setToggle(!toggle);
}
// ...
}
Upvotes: 0
Reputation: 12909
While your specific question of getting the toggle to work has been answered, your example in vanilla javascript hasn't been addressed.
Ideally you wouldn't be hard coding different price options directly into the component, but would be serving an array of products which each held their own specific details including pricing options.
There are various ways of handling this, but in the snippet below your toggle
has become a pointer stored in state in individual Product
components that can be used to access the appropriate price property of the product object passed to that component. eg.
let product = {
id: 1,
name: 'Product One',
defaultPricePoint: 'red',
pricePoints: {
red: "$49.99",
blue: "$69.99",
}
};
const [pricePoint, setPricePoint] = useState('red');
let price = product.pricePoints[pricePoint];
// "$49.99"
The change handler simply sets the pricePoint
state using the value of the input that changed (which were mapped from the keys of the object's prices to begin with). And a useEffect
is implemented to set the initial radio selection based on a default set in the product object.
The result is two fairly simple components and decent separation between data and logic.
(note the use of key
values that are unique for each mapped element, and not index values)
const App = ({ products }) => {
return (
<div>
{products.map(product =>
<Product key={product.id} product={product} />
)}
</div>
)
}
const Product = ({ product }) => {
const [pricePoint, setPricePoint] = React.useState();
React.useEffect(() => {
setPricePoint(product.defaultPricePoint)
}, [product]);
const handlePricePoint = (e) => {
setPricePoint(e.target.value);
}
return (
<div>
<h3>{product.name}</h3>
<p>{product.pricePoints[pricePoint]}</p>
<form>
{Object.keys(product.pricePoints).map(p =>
<div key={product.id + p}>
<input
type="radio"
name="price-point"
value={p}
onChange={handlePricePoint}
checked={pricePoint === p}
/>
<label for={p}>{p}</label>
</div>
)}
</form>
</div>
);
}
ReactDOM.render(
<App products={products} />,
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>
<script>
const products = [
{
id: 1,
name: 'Product One',
defaultPricePoint: 'red',
pricePoints: {
red: "$49.99",
blue: "$69.99",
}
},
{
id: 2,
name: 'Product Two',
defaultPricePoint: 'pro',
pricePoints: {
basic: "$19.99",
pro: "$24.99",
master: "$39.99",
}
}
]
</script>
<div id="root"></div>
Upvotes: 0
Reputation: 202667
The main issue here is that state updates are asynchronous, the toggle
value isn't the updated value you just enqueued.
const handleDiscount = () => {
setToggle(!toggle);
toggle === true ? setPrice(10) : setPrice(20);
};
Use an useEffect
to toggle the price
state when the toggle
state updates.
useEffect(() => {
setPrice(toggle ? 10 : 20);
}, [toggle]);
...
const handleDiscount = () => {
setToggle(toggle => !toggle);
};
Alternatively you can make the checkbox uncontrolled and directly set the price state when the checkbox value updates
const handleDiscount = (e) => {
const { checked } = e.target;
setPrice(checked ? 10 : 20);
};
...
<input
onChange={handleDiscount}
type="checkbox"
className="switch-input"
/>
Upvotes: 1
Reputation: 7905
I am assuming toggle
is a state variable and setToggle
is s state function, in which case, this won't work:
const handleDiscount = () => {
setToggle(!toggle);
toggle === true ? setPrice(10) : setPrice(20);
};
As setToggle is asynchronous. So toggle
does not actually toggle by the time it reaches toggle === true ? setPrice(10) : setPrice(20);
You should instead use useEffect
and listen to the change of toggle
like so:
useEffect(() => {
toggle === true ? setPrice(10) : setPrice(20);
},[toggle])
And get rid of toggle === true ? setPrice(10) : setPrice(20);
inside handleDiscount
altogether.
Upvotes: 0