React中useState点击两次才向服务器发送正确数据问题排查
投票组件点击提交数据的问题
问题背景
点击两张图片之一触发handleClick向服务器提交投票数据,但第一次点击发送的是旧值,第二次点击才能发送正确结果。尝试用useEffect解决时,出现handleClick is not defined的错误;后来按建议拆分handleClick和fetch到独立useEffect,又出现重复提交的情况。
初始代码(handleClick放在useEffect内)
export default function PollVoter() { const [poll, setPoll] = useState({}); const [answer, setAnswer] = useState(); useEffect(() => { function viewingPoll() { // 此处为无关的其他函数逻辑 } viewingPoll(); // 相关逻辑部分: const handleClick = (e) => { e.preventDefault(); console.log("e.target", e.target); if (e.target.name === "A") { (async () => { setAnswer(1); console.log("点击左侧图片"); await fetch("/api/vote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ poll_id: poll.id, answer: answer, }), }); })(); } else if (e.target.name === "B") { (async () => { setAnswer(2); console.log("点击右侧图片"); await fetch("/api/vote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ poll_id: poll.id, answer: answer, }), }); })(); } else { (async () => { setAnswer(0); console.log("未点击有效区域"); await fetch("/api/vote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ poll_id: poll.id, answer: answer, }), }); })(); } }; }, [answer]);
尝试将handleClick移到useEffect外的代码
const handleClick = (e) => { e.preventDefault(); if (e.target.name === "A") { (async () => { setAnswer(1); console.log("点击左侧图片"); await fetch("/api/vote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ poll_id: poll.id, answer: answer, }), }); })(); } else if (e.target.name === "B") { (async () => { setAnswer(2); console.log("点击右侧图片"); await fetch("/api/vote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ poll_id: poll.id, answer: answer, }), }); })(); } }
更新后的代码(拆分逻辑后出现重复提交)
const handleClick = (e) => { e.preventDefault(); console.log("e.target", e.target); if (e.target.name === "A") { (async () => { setAnswer(1); console.log("点击左侧图片"); })(); } // 其他分支逻辑... } // 原有获取投票数据的useEffect useEffect(() => { function viewingPoll() { fetch(`/api/polls/${pollIview}`) // 其他逻辑... } viewingPoll(); }, []); // 新增的处理投票提交的useEffect useEffect( (e) => { fetch("/api/vote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ poll_id: poll.id, answer: answer, }).then(() => { location.reload(); e.target.value === ""; }), })(); }, [answer] );
问题原因与解决办法
1. 第一次提交旧值的原因
setAnswer是异步更新状态的,调用setAnswer后立刻读取answer,拿到的还是更新前的旧值。因此第一次点击时发送的是初始状态(undefined),第二次点击时状态才更新完成,发送正确值。
2. handleClick未定义的原因
handleClick定义在useEffect内部,组件首次渲染时useEffect还未执行,此时图片绑定的点击事件找不到该函数,导致报错。
3. 拆分后重复提交的原因
answer初始值为undefined,组件挂载时useEffect会因初始值触发一次,产生无谓提交;- 第二个
useEffect写法错误:回调函数不能接收参数(e不存在),且JSON.stringify返回的是字符串,没有.then方法,会引发报错导致逻辑异常。
正确实现方式
export default function PollVoter() { const [poll, setPoll] = useState({}); const [answer, setAnswer] = useState(null); // 初始值设为null,区分未选择状态 // 获取投票数据的useEffect useEffect(() => { async function viewingPoll() { const res = await fetch(`/api/polls/${pollIview}`); const data = await res.json(); setPoll(data); } viewingPoll(); }, [pollIview]); // 点击事件处理(直接使用当前选择的值提交) const handleClick = async (e) => { e.preventDefault(); let selectedAnswer; if (e.target.name === "A") { selectedAnswer = 1; console.log("点击左侧图片"); } else if (e.target.name === "B") { selectedAnswer = 2; console.log("点击右侧图片"); } else { selectedAnswer = 0; console.log("未点击有效图片"); } // 直接用selectedAnswer提交,跳过state异步更新等待 try { await fetch("/api/vote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ poll_id: poll.id, answer: selectedAnswer, }), }); setAnswer(selectedAnswer); // 可选:更新state用于后续展示 location.reload(); } catch (err) { console.error("提交投票失败", err); } }; return ( <> <img name="A" onClick={handleClick} src="..." alt="选项A" /> <img name="B" onClick={handleClick} src="..." alt="选项B" /> </> ); }
关键优化点
- 直接使用点击时确定的
selectedAnswer提交,从根源解决异步状态更新导致的旧值问题; handleClick定义在组件顶层,确保渲染时就能被图片点击事件正确绑定;- 用
try/catch捕获fetch异常,避免未处理错误; - 若坚持使用
useEffect方案,需过滤初始null值,避免挂载时触发无效提交。
内容的提问来源于stack exchange,提问作者Ana Ptskialadze
相关产品推荐
相关产品推荐

