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

React笔记应用:实现已固定笔记的取消固定与归位功能

解决React笔记应用固定状态取消问题:让已固定笔记回到原位置

你的问题核心出在三个地方:

  • 用排序后数组的索引定位笔记,导致点击时修改了错误的元素
  • 固定数量限制逻辑写在渲染阶段,引发状态不一致
  • 未保留笔记的原始创建顺序,取消固定后无法回到原位置

下面是具体修复方案和完整代码:

核心修复点

1. 用唯一ID替代索引定位笔记

渲染时的sortedNotes是排序后的数组,索引和原notes数组完全不匹配,用这个索引去修改状态自然找不对目标笔记。改用每个笔记的id定位,彻底避免索引混乱。

2. 把固定数量限制移到点击事件里

原来在sortedNotes的map中修改pinned状态,属于渲染阶段修改状态,会导致React状态更新异常。把限制逻辑放到togglePin函数里,符合状态更新的正确时机。

3. 添加时间戳保留原始顺序

给每个新笔记添加createdAt时间戳,排序时固定笔记在前,未固定笔记按创建时间升序排列,这样取消固定后笔记会回到它最初的位置。

修改后的完整代码

import React, { useState } from "react";
import "../styles/Notes.css";
import { useToast } from '@chakra-ui/react'
import { BsFillPinFill, BsTrashFill, BsPinAngle } from "react-icons/bs";


function NotesComponent() {
  const [notes, setNotes] = useState([]);
  const [title, setTitle] = useState("");
  const [tagline, setTagline] = useState("");
  const [body, setBody] = useState("");
  const [page, setPage] = useState(1);

  const toast = useToast()

  // 生成唯一ID
  const generateUniqueNumber = () => {
    let uniqueNumber = "";
    while (uniqueNumber.length < 4) {
      let digit = Math.floor(Math.random() * 10);
      if (!uniqueNumber.includes(digit)) {
        uniqueNumber += digit;
      }
    }
    return uniqueNumber;
  };

  // 提交笔记
  const handleSubmit = (event) => {
    event.preventDefault();

    if (!title || !tagline || !body) {
      toast({
        title: '请填写完整内容',
        status: 'error',
        duration: 9000,
        isClosable: true,
      });
      return;
    }

    const newNote = {
      title,
      tagline,
      body,
      pinned: false,
      id: generateUniqueNumber(),
      createdAt: Date.now() // 添加创建时间戳,用于维持原始顺序
    };

    setNotes([...notes, newNote]);
    setTitle("");
    setTagline("");
    setBody("");
  };

  // 切换固定状态
  const togglePin = (noteId) => {
    setNotes(prevNotes => {
      const targetNote = prevNotes.find(note => note.id === noteId);
      if (!targetNote) return prevNotes;

      const pinnedCount = prevNotes.filter(n => n.pinned).length;
      
      // 固定数量限制:最多2条
      if (!targetNote.pinned && pinnedCount >= 2) {
        toast({
          title: '最多只能固定2条笔记',
          status: 'warning',
          duration: 3000,
          isClosable: true,
        });
        return prevNotes;
      }

      // 切换目标笔记的固定状态
      return prevNotes.map(note => 
        note.id === noteId ? {...note, pinned: !note.pinned} : note
      );
    });
  };

  // 排序规则:固定笔记在前,未固定笔记按创建时间升序排列
  const sortedNotes = [...notes].sort((a, b) => {
    if (a.pinned !== b.pinned) {
      return a.pinned ? -1 : 1;
    }
    return a.createdAt - b.createdAt;
  });

  // 分页处理
  const pages = [1, 2, 3, 4, 5, 6];
  const pageChangeHandler = (e) => {
    setPage(parseInt(e.target.innerText));
  };

  // 删除笔记
  const deleteHandler = (noteId) => {
    setNotes(prevNotes => prevNotes.filter(note => note.id !== noteId));
  };

  return (
    <div className="notes-app-container">
      <form onSubmit={handleSubmit} className="notes-form">
        <input
          type="text"
          placeholder="标题"
          value={title}
          onChange={(event) => setTitle(event.target.value)}
          className="notes-input"
        />
        <input
          type="text"
          placeholder="副标题"
          value={tagline}
          onChange={(event) => setTagline(event.target.value)}
          className="notes-input"
        />
        <textarea
          placeholder="内容"
          value={body}
          onChange={(event) => setBody(event.target.value)}
          className="notes-textarea"
        />
        <button type="submit" className="notes-button">
          添加笔记
        </button>
      </form>
      <div className="enteredNotesMainParent">
        {sortedNotes.slice((page - 1) * 6, page * 6).map((note) => (
          <div key={note.id} className="enteredNoteIndivitual">
            <div>{note.title}</div>
            <div>{note.tagline}</div>
            <div>{note.body}</div>
            <br />
            <div className="noteCtaHold">
              <div>
                {/* 根据固定状态切换图标,提升用户感知 */}
                {note.pinned ? (
                  <BsFillPinFill className="noteIcon" onClick={() => togglePin(note.id)} />
                ) : (
                  <BsPinAngle className="noteIcon" onClick={() => togglePin(note.id)} />
                )}
              </div>
              <div>
                <BsTrashFill className="noteIcon" onClick={() => deleteHandler(note.id)} />
              </div>
            </div>
          </div>
        ))}
      </div>

      {notes.length === 0 ? <p> 快来添加笔记吧✅ </p> : ""}

      {notes.length >= 6 && (
        <div className="pagesHold">
          {pages.map((item) => (
            <p
              key={item}
              onClick={pageChangeHandler}
              className={`indivitualPage ${page === item ? 'active' : ''}`}
            >
              {item}
            </p>
          ))}
        </div>
      )}
    </div>
  );
}

export default NotesComponent;

额外优化说明

  • 修正了分页切片的起始索引,避免分页逻辑错误
  • 根据固定状态显示不同图标,让用户直观感知当前状态
  • 把生成ID的函数独立出来,提升代码可读性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 01:31:22