React 中的切换按钮不起作用,尽管它达到了目的

问题描述 投票:0回答:1

在这个挑战的步骤#2中,有一个切换按钮,它用自己的风格完成它,它应该给出每月值或每年值,这里是代码:

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>
  );
}

数据.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 };

这是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);
}

虽然值正确变化,但它没有向右移动..有什么帮助吗?

我尝试使用 onClick 代替,但它总是一样。

css reactjs input togglebutton
1个回答
0
投票

考虑删除

preventDefault()
中的
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>

您也不应该阅读

changeBilling()
中之前的状态,因为它可能已经过时。相反,从
setFormInfo
函数内部读取状态:

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>

© www.soinside.com 2019 - 2024. All rights reserved.