我知道很多类似的问题已经被问到并得到了回答,但是我有一个 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);
}
}
};
我在这里做错了什么?
您的代码的问题是您更新状态
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);
};
下面是两个问题的解决方案: 沙盒链接
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}
/>
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
})
}
您需要更改
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>
);
}