You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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('')的异步更新被组件重新渲染的过程覆盖,导致输入框状态看起来未变化。
  • 修复步骤:
    1. 将输入框改为受控组件,给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 12:27:13