cssreactjsinputtogglebutton

Toggle button in React doesn't work although it does its purpose


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.


Solution

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