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

React Hooks实现可编辑书单时单字段编辑引发另一字段异常问题

修复你的书单编辑功能异常问题

我来帮你搞定这个编辑功能的bug!你的问题主要出在编辑状态同步和状态更新逻辑上,下面一步步拆解问题并给出修复方案:

核心问题分析

  1. 编辑模式未初始化输入状态:点击编辑按钮时,你没有把当前书籍的标题和作者同步到titleValue和authorValue状态里。这就导致如果用户只修改一个字段,另一个字段的状态还是初始空值,保存时直接覆盖原内容为空。
  2. 直接修改原状态对象:在saveChanges函数里,你直接修改了数组中的book对象(book.title = titleValue),这违反了React状态不可变的规则,容易引发不可预测的状态更新。
  3. 状态更新目标错误:setBook(newBook)是完全错误的——newBook是修改后的书籍数组,而book状态是单个书籍对象,应该用setBooks来更新整个书单列表。
  4. useEffect依赖缺失:第二个useEffect没有依赖books,会导致组件每次渲染都执行localStorage.setItem,这既浪费性能也可能引发异常。
  5. 编辑表单未做双向绑定:编辑表单用了defaultValue,但没有和titleValue/authorValue绑定,导致输入框内容和状态不同步。

修复后的完整代码

import './App.css';
import React, { useState, useEffect } from 'react';

function App() {
  const [books, setBooks] = useState([]);
  const [book, setBook] = useState({ id: '', title: '', author: '' });
  const [alertMessage, setAlertMessage] = useState(false);
  const [successMessage, setSuccessMessage] = useState(false);
  const [editMode, setEditMode] = useState(null);
  const [titleValue, setTitleValue] = useState('');
  const [authorValue, setAuthorValue] = useState('');

  useEffect(() => {
    const data = localStorage.getItem('books');
    if (data) {
      setBooks(JSON.parse(data));
    }
  }, []);

  // 修复:添加books作为依赖项
  useEffect(() => {
    localStorage.setItem('books', JSON.stringify(books));
  }, [books]);

  function addBook(e) {
    e.preventDefault();
    if (!book.title && !book.author) {
      setAlertMessage(true);
      setTimeout(() => setAlertMessage(false), 3000);
    } else {
      let newBook = {
        ...book,
        id: Math.floor(Math.random() * 100000000),
      };
      setBooks([newBook, ...books]);
      setBook({ title: '', author: '' });
      setSuccessMessage(true);
      setTimeout(() => setSuccessMessage(false), 1000);
    }
  }

  function deleteBook(id) {
    setBooks(books.filter(book => book.id !== id));
  }

  // 修复:进入编辑模式时,同步当前书籍的数据到输入状态
  function editBook(id) {
    const targetBook = books.find(book => book.id === id);
    if (targetBook) {
      setTitleValue(targetBook.title);
      setAuthorValue(targetBook.author);
      setEditMode(id);
    }
  }

  function onChange(e) {
    setBook({ ...book, [e.target.name]: e.target.value });
  }

  function saveChanges(id) {
    // 修复:创建新数组,不直接修改原对象
    const updatedBooks = books.map(book => {
      if (book.id === id) {
        // 返回新对象,不修改原状态
        return {
          ...book,
          title: titleValue || book.title, // 如果没修改,保留原标题
          author: authorValue || book.author // 如果没修改,保留原作者
        };
      }
      return book;
    });
    // 修复:更新books状态,而不是book状态
    setBooks(updatedBooks);
    setEditMode(null);
    // 清空输入状态,准备下一次编辑
    setTitleValue('');
    setAuthorValue('');
  }

  return (
    <div className='container'>
      {alertMessage && <div className='alertMeaage'>Please, enter book author or its title</div>}
      {successMessage && <div className='successMessage'>Book is successfully added!</div>}
      <div className='BookForm'>
        <h3>Add book</h3>
        <input name='title' type='text' placeholder='Enter book title' value={book.title} onChange={onChange}/>
        <input name='author' type='text' placeholder='Enter book author' value={book.author} onChange={onChange}/>
        <button className='submitBtn' onClick={addBook}>Send</button>
      </div>
      <div>
        <h4>Recently added books:</h4>
        {/* 修复:把key放在map返回的元素上,不要放在外层div */}
        {books.map(book => (
          <div key={book.id} className='bookItem'>
            {editMode !== book.id ? (
              <>
                <span className='titleAuthor'>Title: </span>
                <i>«{book.title}» </i>
                <span className='titleAuthor'>Author: </span>
                <i>{book.author}</i>
                <button onClick={() => deleteBook(book.id)} className='deleteBtn'>X</button>
                <button onClick={() => editBook(book.id)} className='editBtn'>Edit</button>
              </>
            ) : (
              <form className='form' onSubmit={(e) => {
                e.preventDefault();
                saveChanges(book.id);
              }}>
                {/* 修复:用value绑定状态,实现双向同步 */}
                <input 
                  name='title' 
                  type='text' 
                  value={titleValue} 
                  onChange={(e) => setTitleValue(e.target.value)}
                  placeholder='Edit title'
                />
                <input 
                  name='author' 
                  type='text' 
                  value={authorValue} 
                  onChange={(e) => setAuthorValue(e.target.value)}
                  placeholder='Edit author'
                />
                <button className='saveBtn' type='submit'>Save</button>
              </form>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

export default App;

关键修改说明

  1. 编辑模式初始化:在editBook函数里,通过books.find找到要编辑的书籍,把它的title和author赋值给对应的状态,确保编辑时输入框显示原内容,状态同步。
  2. 不可变状态更新:在saveChanges里,用map返回新的书籍对象({...book, title: ..., author: ...}),不直接修改原数组里的对象,符合React状态管理规则。
  3. 状态更新修正:把setBook(newBook)改成setBooks(updatedBooks),正确更新书单列表状态。
  4. useEffect依赖修复:给第二个useEffect添加[books]依赖,只有当书单变化时才更新localStorage。
  5. 编辑表单双向绑定:把编辑表单的defaultValue改成value绑定titleValue/authorValue,确保输入框内容和状态完全同步;同时给表单添加onSubmit事件,避免点击按钮时触发默认表单提交行为。
  6. key属性修正:把key从外层div移到map返回的bookItem上,符合React列表渲染的最佳实践。

现在你可以测试一下:点击编辑后,只修改标题或者只修改作者,另一个字段都会保留原内容,不会被清空啦!

内容的提问来源于stack exchange,提问作者semen_nova

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 22:29:08