In the step #2 in this challeng, there is a toggle button it did it with it own style and it supposed to give a montly value or yearly value and here is the code:
import { NavLink } from "react-router-dom";
import { useContext } from "react";
import data from "../Data";
import { dataContext } from "../context/DataContext";
export default function Plans() {
const { formInfo, setFormInfo, setStep, currStep } = useContext(dataContext);
const { isMonthly } = formInfo;
const handleChange = e => {
const currPlan = data.plans.filter(plan => plan.name === e.target.value);
const currPrice = isMonthly
? currPlan[0].monthlyPrice
: currPlan[0].yearlyPrice;
setFormInfo(prev => ({
...prev,
plan: {
planName: e.target.value,
planPrice: currPrice,
},
}));
};
const handleSubmit = () => {
setStep(3);
};
const goBack = () => {
setStep(currStep - 1);
};
const changeBilling = e => {
e.preventDefault();
const infoPlan = formInfo.plan.planName;
const currPlan = data.plans.filter(plan => plan.name === infoPlan)[0];
const currPrice = !isMonthly ? currPlan.monthlyPrice : currPlan.yearlyPrice;
const infoAddon = formInfo.addons;
let newAddons = [];
if (infoAddon.length > 0) {
for (const item of infoAddon) {
const theAddOnInfo = data.addons.filter(
info => info.name === item.name
);
newAddons.push({
name: item.name,
price: !isMonthly
? theAddOnInfo[0].monthlyPrice
: theAddOnInfo[0].yearlyPrice,
});
}
}
setFormInfo(prev => ({
...prev,
isMonthly: !prev.isMonthly,
plan: {
...prev.plan,
planPrice: currPrice,
},
addons: newAddons,
}));
};
console.log(formInfo);
return (
<div className="plans">
<h1>Select your plan</h1>
<p>You have the option of monthly or yearly billing.</p>
<form method="post" onSubmit={handleSubmit}>
<div className="form-container">
{data.plans.map(item => {
return (
<label htmlFor={item.name.toLowerCase()} key={item.name}>
<input
type="radio"
name="plan"
id={item.name.toLowerCase()}
value={item.name}
onChange={e => handleChange(e)}
checked={item.name === formInfo.plan.planName}
/>
<div className="values">
{item.name}
{isMonthly ? (
<p>${item.monthlyPrice}/mo</p>
) : (
<p>${item.yearlyPrice}/yr</p>
)}
{!isMonthly && <p>2 months free</p>}
</div>
</label>
);
})}
</div>
<div>
<p>Monthly</p>
<label className="switch">
<input
type="checkbox"
defaultChecked={!isMonthly}
value={isMonthly ? "Montly" : "Yearly"}
onChange={e => changeBilling(e)}
/>
<span className="slider round"></span>
</label>
<p>Yearly</p>
</div>
<button>Next Step</button>
</form>
<NavLink to="#" onClick={() => goBack()}>
Go Back
</NavLink>
</div>
);
}
Data.json
const data = {
plans: [
{
name: "Arcade",
img: "./assets/images/icon-arcade.svg",
monthlyPrice: 9,
yearlyPrice: 90,
},
{
name: "Advanced",
img: "./assets/images/icon-advanced.svg",
monthlyPrice: 12,
yearlyPrice: 120,
},
{
name: "Pro",
img: "./assets/images/icon-pro.svg",
monthlyPrice: 15,
yearlyPrice: 150,
},
],
addons: [
{
name: "Online service",
description: "Access to multiplayer games",
monthlyPrice: 1,
yearlyPrice: 10,
id: "service",
},
{
name: "Larger Storage",
description: "Extra 1TB of cloud save",
monthlyPrice: 2,
yearlyPrice: 20,
id: "storage",
},
{
name: "Customizable profile",
description: "Custom theme on your profile",
monthlyPrice: 2,
yearlyPrice: 20,
id: "profile",
},
],
};
export default data;
DataContext.js
import { createContext, useState } from "react";
const dataContext = createContext();
function ContextProvider({ children }) {
const [currStep, setStep] = useState(1);
const [isConfirmed, setIsConfirmed] = useState(false);
const [formInfo, setFormInfo] = useState({
name: "",
email: "",
phone: "",
plan: {
planName: "Arcade",
planPrice: 9,
},
addons: [],
isMonthly: true,
});
return (
<dataContext.Provider
value={{
currStep,
setStep,
isConfirmed,
setIsConfirmed,
formInfo,
setFormInfo,
}}
>
{children}
</dataContext.Provider>
);
}
export { ContextProvider, dataContext };
here is the css
.plans form > div {
display: flex;
background-color: var(--alabaster);
padding: 12px;
border-radius: 10px;
margin-top: 20px;
justify-content: center;
font-size: 10px;
}
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 18px;
margin: 0 20px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider.round {
border-radius: 18px;
}
.slider.round:before {
border-radius: 50%;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--marineBlue);
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 12px;
width: 12px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.4s;
}
input:checked + .slider {
background-color: var(--marineBlue);
}
/* input:not(:is(:checked)) + .slider:before {
transform: translateX(0px);
} */
input:checked + .slider:before {
transform: translateX(21px);
}
.form-control {
border: 1px var(--lightGray) solid;
padding: 15px;
border-radius: 6px;
cursor: pointer;
color: var(--marineBlue);
display: grid;
grid-template-columns: 1em auto;
gap: 20px;
margin-bottom: 15px;
}
.flex-container {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 1.4;
}
.flex-container p span {
display: flex;
align-items: flex-start;
font-size: 14px;
color: var(--coolGray);
}
It dosen't move right althought the value changes properly..any help ?
I tried to use onClick instead but it always the same.
Consider removing the preventDefault()
call in changeBilling()
:
const { useContext } = React;
const data = {
plans: [
{
name: "Arcade",
img: "./assets/images/icon-arcade.svg",
monthlyPrice: 9,
yearlyPrice: 90,
},
{
name: "Advanced",
img: "./assets/images/icon-advanced.svg",
monthlyPrice: 12,
yearlyPrice: 120,
},
{
name: "Pro",
img: "./assets/images/icon-pro.svg",
monthlyPrice: 15,
yearlyPrice: 150,
},
],
addons: [
{
name: "Online service",
description: "Access to multiplayer games",
monthlyPrice: 1,
yearlyPrice: 10,
id: "service",
},
{
name: "Larger Storage",
description: "Extra 1TB of cloud save",
monthlyPrice: 2,
yearlyPrice: 20,
id: "storage",
},
{
name: "Customizable profile",
description: "Custom theme on your profile",
monthlyPrice: 2,
yearlyPrice: 20,
id: "profile",
},
],
};
const { createContext, useState } = React;
const dataContext = createContext();
function ContextProvider({ children }) {
const [currStep, setStep] = useState(1);
const [isConfirmed, setIsConfirmed] = useState(false);
const [formInfo, setFormInfo] = useState({
name: "",
email: "",
phone: "",
plan: {
planName: "Arcade",
planPrice: 9,
},
addons: [],
isMonthly: true,
});
return (
<dataContext.Provider
value={{
currStep,
setStep,
isConfirmed,
setIsConfirmed,
formInfo,
setFormInfo,
}}
>
{children}
</dataContext.Provider>
);
}
function Plans() {
const { formInfo, setFormInfo, setStep, currStep } = useContext(dataContext);
const { isMonthly } = formInfo;
const handleChange = e => {
const currPlan = data.plans.filter(plan => plan.name === e.target.value);
const currPrice = isMonthly
? currPlan[0].monthlyPrice
: currPlan[0].yearlyPrice;
setFormInfo(prev => ({
...prev,
plan: {
planName: e.target.value,
planPrice: currPrice,
},
}));
};
const handleSubmit = () => {
setStep(3);
};
const goBack = () => {
setStep(currStep - 1);
};
const changeBilling = e => {
const infoPlan = formInfo.plan.planName;
const currPlan = data.plans.filter(plan => plan.name === infoPlan)[0];
const currPrice = !isMonthly ? currPlan.monthlyPrice : currPlan.yearlyPrice;
const infoAddon = formInfo.addons;
let newAddons = [];
if (infoAddon.length > 0) {
for (const item of infoAddon) {
const theAddOnInfo = data.addons.filter(
info => info.name === item.name
);
newAddons.push({
name: item.name,
price: !isMonthly
? theAddOnInfo[0].monthlyPrice
: theAddOnInfo[0].yearlyPrice,
});
}
}
setFormInfo(prev => ({
...prev,
isMonthly: !prev.isMonthly,
plan: {
...prev.plan,
planPrice: currPrice,
},
addons: newAddons,
}));
};
console.log(isMonthly);
return (
<div className="plans">
<h1>Select your plan</h1>
<p>You have the option of monthly or yearly billing.</p>
<form method="post" onSubmit={handleSubmit}>
<div className="form-container">
{data.plans.map(item => {
return (
<label htmlFor={item.name.toLowerCase()} key={item.name}>
<input
type="radio"
name="plan"
id={item.name.toLowerCase()}
value={item.name}
onChange={e => handleChange(e)}
checked={item.name === formInfo.plan.planName}
/>
<div className="values">
{item.name}
{isMonthly ? (
<p>${item.monthlyPrice}/mo</p>
) : (
<p>${item.yearlyPrice}/yr</p>
)}
{!isMonthly && <p>2 months free</p>}
</div>
</label>
);
})}
</div>
<div>
<p>Monthly</p>
<label className="switch">
<input
type="checkbox"
defaultChecked={!isMonthly}
value={isMonthly ? "Montly" : "Yearly"}
onChange={e => changeBilling(e)}
/>
<span className="slider round"></span>
</label>
<p>Yearly</p>
</div>
<button>Next Step</button>
</form>
<a href="#" onClick={() => goBack()}>
Go Back
</a>
</div>
);
}
function App() {
return <ContextProvider><Plans/></ContextProvider>;
}
ReactDOM.createRoot(document.getElementById('app')).render(<App/>);
:root {
--alabaster: #FCF3CF;
--marineBlue: #AED6F1;
--lightGray: #EAECEE;
--coolGray: #D5D8DC;
}
.plans form>div {
display: flex;
background-color: var(--alabaster);
padding: 12px;
border-radius: 10px;
margin-top: 20px;
justify-content: center;
font-size: 10px;
}
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 18px;
margin: 0 20px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider.round {
border-radius: 18px;
}
.slider.round:before {
border-radius: 50%;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--marineBlue);
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 12px;
width: 12px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.4s;
}
input:checked+.slider {
background-color: var(--marineBlue);
}
/* input:not(:is(:checked)) + .slider:before {
transform: translateX(0px);
} */
input:checked+.slider:before {
transform: translateX(21px);
}
.form-control {
border: 1px var(--lightGray) solid;
padding: 15px;
border-radius: 6px;
cursor: pointer;
color: var(--marineBlue);
display: grid;
grid-template-columns: 1em auto;
gap: 20px;
margin-bottom: 15px;
}
.flex-container {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 1.4;
}
.flex-container p span {
display: flex;
align-items: flex-start;
font-size: 14px;
color: var(--coolGray);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="app"></div>
You should also not read previous state inside changeBilling()
, as it could be stale. Instead, read the state from inside the setFormInfo
function:
const { useContext } = React;
const data = {
plans: [
{
name: "Arcade",
img: "./assets/images/icon-arcade.svg",
monthlyPrice: 9,
yearlyPrice: 90,
},
{
name: "Advanced",
img: "./assets/images/icon-advanced.svg",
monthlyPrice: 12,
yearlyPrice: 120,
},
{
name: "Pro",
img: "./assets/images/icon-pro.svg",
monthlyPrice: 15,
yearlyPrice: 150,
},
],
addons: [
{
name: "Online service",
description: "Access to multiplayer games",
monthlyPrice: 1,
yearlyPrice: 10,
id: "service",
},
{
name: "Larger Storage",
description: "Extra 1TB of cloud save",
monthlyPrice: 2,
yearlyPrice: 20,
id: "storage",
},
{
name: "Customizable profile",
description: "Custom theme on your profile",
monthlyPrice: 2,
yearlyPrice: 20,
id: "profile",
},
],
};
const { createContext, useState } = React;
const dataContext = createContext();
function ContextProvider({ children }) {
const [currStep, setStep] = useState(1);
const [isConfirmed, setIsConfirmed] = useState(false);
const [formInfo, setFormInfo] = useState({
name: "",
email: "",
phone: "",
plan: {
planName: "Arcade",
planPrice: 9,
},
addons: [],
isMonthly: true,
});
return (
<dataContext.Provider
value={{
currStep,
setStep,
isConfirmed,
setIsConfirmed,
formInfo,
setFormInfo,
}}
>
{children}
</dataContext.Provider>
);
}
function Plans() {
const { formInfo, setFormInfo, setStep, currStep } = useContext(dataContext);
const { isMonthly } = formInfo;
const handleChange = e => {
const currPlan = data.plans.filter(plan => plan.name === e.target.value);
const currPrice = isMonthly
? currPlan[0].monthlyPrice
: currPlan[0].yearlyPrice;
setFormInfo(prev => ({
...prev,
plan: {
planName: e.target.value,
planPrice: currPrice,
},
}));
};
const handleSubmit = () => {
setStep(3);
};
const goBack = () => {
setStep(currStep - 1);
};
const changeBilling = e => {
setFormInfo(prev => {
const infoPlan = prev.plan.planName;
const currPlan = data.plans.filter(plan => plan.name === infoPlan)[0];
const currPrice = !prev.isMonthly ? currPlan.monthlyPrice : currPlan.yearlyPrice;
const infoAddon = prev.addons;
let newAddons = [];
if (infoAddon.length > 0) {
for (const item of infoAddon) {
const theAddOnInfo = data.addons.filter(
info => info.name === item.name
);
newAddons.push({
name: item.name,
price: !prev.isMonthly
? theAddOnInfo[0].monthlyPrice
: theAddOnInfo[0].yearlyPrice,
});
}
}
return {
...prev,
isMonthly: !prev.isMonthly,
plan: {
...prev.plan,
planPrice: currPrice,
},
addons: newAddons,
};
});
};
console.log(isMonthly);
return (
<div className="plans">
<h1>Select your plan</h1>
<p>You have the option of monthly or yearly billing.</p>
<form method="post" onSubmit={handleSubmit}>
<div className="form-container">
{data.plans.map(item => {
return (
<label htmlFor={item.name.toLowerCase()} key={item.name}>
<input
type="radio"
name="plan"
id={item.name.toLowerCase()}
value={item.name}
onChange={e => handleChange(e)}
checked={item.name === formInfo.plan.planName}
/>
<div className="values">
{item.name}
{isMonthly ? (
<p>${item.monthlyPrice}/mo</p>
) : (
<p>${item.yearlyPrice}/yr</p>
)}
{!isMonthly && <p>2 months free</p>}
</div>
</label>
);
})}
</div>
<div>
<p>Monthly</p>
<label className="switch">
<input
type="checkbox"
defaultChecked={!isMonthly}
value={isMonthly ? "Montly" : "Yearly"}
onChange={e => changeBilling(e)}
/>
<span className="slider round"></span>
</label>
<p>Yearly</p>
</div>
<button>Next Step</button>
</form>
<a href="#" onClick={() => goBack()}>
Go Back
</a>
</div>
);
}
function App() {
return <ContextProvider><Plans/></ContextProvider>;
}
ReactDOM.createRoot(document.getElementById('app')).render(<App/>);
:root {
--alabaster: #FCF3CF;
--marineBlue: #AED6F1;
--lightGray: #EAECEE;
--coolGray: #D5D8DC;
}
.plans form>div {
display: flex;
background-color: var(--alabaster);
padding: 12px;
border-radius: 10px;
margin-top: 20px;
justify-content: center;
font-size: 10px;
}
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 18px;
margin: 0 20px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider.round {
border-radius: 18px;
}
.slider.round:before {
border-radius: 50%;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--marineBlue);
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 12px;
width: 12px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.4s;
}
input:checked+.slider {
background-color: var(--marineBlue);
}
/* input:not(:is(:checked)) + .slider:before {
transform: translateX(0px);
} */
input:checked+.slider:before {
transform: translateX(21px);
}
.form-control {
border: 1px var(--lightGray) solid;
padding: 15px;
border-radius: 6px;
cursor: pointer;
color: var(--marineBlue);
display: grid;
grid-template-columns: 1em auto;
gap: 20px;
margin-bottom: 15px;
}
.flex-container {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 1.4;
}
.flex-container p span {
display: flex;
align-items: flex-start;
font-size: 14px;
color: var(--coolGray);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="app"></div>