React useState状态不更新求助:Firebase Todo应用调试
问题
我在第28行使用React Hook useState() 创建了状态:const [input, setInput] = useState('');,随后在createTodo函数中调用setInput('')更新状态,但执行后状态并未更新。控制台无报错,状态更新语句后的console.log()能执行,但状态仍未变化。
App.jsx
import React, { useEffect, useState } from 'react'; import { AiOutlinePlus } from 'react-icons/ai'; import ToDo from './ToDo'; import { db } from './firebase'; import { addDoc, collection, deleteDoc, doc, onSnapshot, query, updateDoc, } from 'firebase/firestore'; const style = { bg: `h-screen w-screen p-4 bg-gradient-to-r from-[#2F80ED] to-[#1CB5E0]`, container: `bg-slate-100 max-w-[500px] w-full m-auto rounded-md shadow-xl p-4`, heading: `text-3xl font-bold text-center text-gray-800 p-2`, form: `flex justify-between`, input: `border p-2 w-full text-xl`, button: `border p-4 ml-2 bg-purple-500 text-slate-100`, count: `text-center p-2`, }; const App = () => { const [todos, setTodos] = useState([]); const [input, setInput] = useState(''); // 目标状态 // ************ firebase 操作 ************ // 创建待办 const createTodo = async event => { event.preventDefault(); if (input === '') { alert('请输入内容'); return; } // 写入firebase await addDoc(collection(db, 'todos'), { text: input, completed: false, }); setInput(''); // 此处状态未更新 }; // 读取待办 useEffect(() => { const q = query(collection(db, 'todos')); const unsubscribe = onSnapshot(q, querySnapshot => { let todosArr = []; querySnapshot.forEach(doc => { todosArr.push({ ...doc.data(), id: doc.id }); }); setTodos(todosArr); }); return () => unsubscribe(); }, []); // 更新完成状态 const toggleComplete = async todo => { await updateDoc(doc(db, 'todos', todo.id), { completed: !todo.completed, }); }; // 删除待办 const todoDelete = async id => { await deleteDoc(doc(db, 'todos', id)); }; return ( <div className={style.bg}> <div className={style.container}> <h2 className={style.heading}>ToDo CRUD with Firebase</h2> <form onSubmit={createTodo} className={style.form}> <input type="text" className={style.input} placeholder="ToDo" onChange={event => setInput(event.target.value)} /> <button className={style.button}> <AiOutlinePlus size={30} /> </button> </form> <ul> {todos.map((todo, index) => ( <ToDo key={index} todo={todo} toggleComplete={toggleComplete} todoDelete={todoDelete} /> ))} </ul> {todos.length === 0 ? null : ( <p className={style.count}>{`You have ${todos.length} todos`}</p> )} </div> </div> ); }; export default App;
解决方案
- 问题原因:你使用
onSnapshot监听Firestore集合变化,当addDoc写入新待办后,onSnapshot会立即触发并调用setTodos更新组件状态,引发组件重新渲染。而React的状态更新是异步的,setInput('')的异步更新被组件重新渲染的过程覆盖,导致输入框状态看起来未变化。 - 修复步骤:
- 将输入框改为受控组件,给
input元素添加value属性绑定input状态:
这样设置后,即使组件因<input type="text" className={style.input} placeholder="ToDo" onChange={event => setInput(event.target.value)} value={input} // 新增该行,让输入框值完全由状态控制 />onSnapshot触发重新渲染,输入框也会同步input的最新值(空字符串)。
- 将输入框改为受控组件,给
- 额外优化:不要用数组索引作为列表项的
key,改用Firestore返回的doc.id,避免列表渲染时出现异常:{todos.map(todo => ( <ToDo key={todo.id} // 替换为todo.id todo={todo} toggleComplete={toggleComplete} todoDelete={todoDelete} /> ))}
内容的提问来源于stack exchange,提问作者Mahbubul Hasan
相关产品推荐
相关产品推荐

