反应单选按钮和状态未按预期执行

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

我知道很多类似的问题已经被问到并得到了回答,但是我有一个 React 组件,它的单选按钮没有按预期执行......

const availableClasses = {
  peasant: {
    name: "Peasant",
    cost: 0,
  },
  noble: {
    name: "Noble",
    cost: 12,
  },
  warrior: {
    name: "Warrior",
    cost: 8,
  },
  wizard: {
    name: "Wizard",
    cost: 9,
  },
  trader: {
    name: "Trader",
    cost: 10,
  }
};

...

{Object.entries(availableClasses).map(([key, value]) => (
  <div key={key} className="form-control">
    <label className="label cursor-pointer">
      <span className="label-text">{value.name} - {value.cost}p</span>
      <input
        type="radio"
        name="charClass"
        value={key}
        className="radio checked:background-green-500"
        checked={key === charClass}
        onChange={handleClassChange}
      />
    </label>
  </div>
))}

当用户单击单选按钮时,应从总数中扣除一定数量的点数。如果用户随后选择了不同的收音机,则应将之前的点数返回到总点数,并从其总点数中扣除不同数量的点数。但是,我似乎无法在用户选择不同的收音机后让状态更新总点数,而且我无法弄清楚我做错了什么。我已经搜索了 2 天,我发现的所有内容看起来都应该可以正常工作,但事实并非如此,并且没有抛出任何错误。这是我的处理程序:

const handleClassChange = (e: any) => {
  const cls = e.target.value;
  
  if(cls != charClass){
    const refund = freePoints + lastUsed;
    setLast(0);
    setFreePoints(refund);
    
    const cost = availableClasses[cls].cost;
    setLast(cost);
    
    const query = (freePoints - cost) < 0;
    const error = "You do not have enough free points!";
    const cause = freePoints - cost;
    
    if (query) {
      toast.error(error, {
        position: toast.POSITION.BOTTOM_RIGHT,
        draggable: false,
      });
    } else {
      setCharClass(cls);
      setFreePoints(cause);
    }
  }
};

我在这里做错了什么?

reactjs next.js radio-button
3个回答
1
投票

您的代码的问题是您更新状态

setFreePoints(refund);
并期望它立即生效。在反应中,状态将在反应重新渲染时更新。因此,退款后的
freePoints
仍然具有之前的状态值。

...
    const refund = freePoints + lastUsed;
    setLast(0); // this code is unnecessary since you set the state again below
    setFreePoints(refund); // here is the main problem
    
    const cost = availableClasses[cls].cost;
    setLast(cost);
    
    const cause = freePoints - cost; // freePoints still has the old value without refund
...

你可以解决它:

const handleClassChange = (e: any) => {
  const cls = e.target.value;
  
  if(cls != charClass){
    const refund = freePoints + lastUsed;
    const cost = availableClasses[cls].cost;
    
    const query = (refund - cost) < 0;
    const error = "You do not have enough free points!";
    const cause = refund - cost;
    
    if (query) {
      toast.error(error, {
        position: toast.POSITION.BOTTOM_RIGHT,
        draggable: false,
      });
    } else {
      setLast(cost); // last will always start at 0, so we don't need to reset it after a radio clicked
      setCharClass(cls);
      setFreePoints(cause);
    }
  }
};

或者,我认为你可以改变方法(但这可能会导致意想不到的副作用),因为我们使用的是

radio
一次只会激活一个,而不是做
refund
你可以从
 中扣除total
每次更改事件。

例如:

const initialFreePoints = 100 // this is just example
const [freePoints, setFreePoints] = useState(initialFreePoints)
const handleClassChange = (e: any) => {
  const cls = e.target.value;
  const cost = availableClasses[cls].cost;
  const cause = initialFreePoints - cost;
  const query = cause < 0;
  const error = "You do not have enough free points!";
  if (query) {
    toast.error(error, {
      position: toast.POSITION.BOTTOM_RIGHT,
      draggable: false,
    });
    return;
  }
  setCharClass(cls);
  setFreePoints(cause);
};

0
投票

下面是两个问题的解决方案: 沙盒链接

  1. 您想为收音机设置
    selected
    attr。您当前正在设置
    checked
    checked
    attr 用于复选框,而
    selected
    attr 用于单选按钮

<input
    type="radio"
    name="charClass"
    value={key}
    className="radio checked:background-green-500"
    // You should set the selected attr not checked
    selected={key === characterClass}
    onChange={handleCharChange}
/>

  1. 当一个状态依赖于另一个状态时,您应该使用函数式 setState 签名。

const handleCharChange = (e) => {
    const activeChar = e.target.value;
    const activeCost = availableClasses[activeChar].cost
    // Use the functional signature of setState when updating 
    // other states that depends on another state
    setCharClass(prev => {
      // This logic will update the Total
      if (prev) {
        const newTotal = (total - availableClasses[prev].cost) + activeCost;
        setTotal(newTotal)
      } else {
        setTotal(total + activeCost)
      }
      // This will set Character Class
      return activeChar
    })
  }


-1
投票

您需要更改

handler
本身的逻辑,与
state
更新无关,这里它应该适用于此修改

import React, { useState } from "react";
import { useForm } from "react-hook-form";
import "./styles.css";
const availableClasses = {
  peasant: {
    name: "Peasant",
    cost: 0
  },
  noble: {
    name: "Noble",
    cost: 12
  },
  warrior: {
    name: "Warrior",
    cost: 8
  },
  wizard: {
    name: "Wizard",
    cost: 9
  },
  trader: {
    name: "Trader",
    cost: 10001
  }
};
export default function App({ login }) {
  const [lastUsed, setLast] = useState(0);
  const [freePoints, setFreePoints] = useState(100);
  const [charClass, setCharClass] = useState("");
  console.log("last", lastUsed, "freePo", freePoints, "charcls", charClass);
  const handleClassChange = (e, value) => {
    const cls = e.target.value;
    const refund = freePoints + lastUsed;
    const cost = value.cost;
   //Calculate with refund
    const query = refund - cost < 0;
    const error = "You do not have enough free points!";
    const cause = refund - cost;
    if (query) {
      toast.error(error, {
        position: toast.POSITION.BOTTOM_RIGHT,
        draggable: false,
      });
    } else {
      setLast(cost);
      setCharClass(cls);
      setFreePoints(cause);
    }
  };

  return (
    <div>
      {Object.entries(availableClasses).map(([key, value]) => (
        <div key={key} className="form-control">
          <label className="label cursor-pointer">
            <span className="label-text">
              {value.name} - {value.cost}p
            </span>
            <input
              type="radio"
              name="charClass"
              value={key}
              className="radio checked:background-green-500"
              checked={key === charClass}
             //Pass value here itself
              onChange={(e) => handleClassChange(e, value)}
            />
          </label>
        </div>
      ))}
    </div>
  );
}
© www.soinside.com 2019 - 2024. All rights reserved.