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

React笔记应用MyModal组件输入失焦与自动聚焦问题求助

React笔记应用MyModal组件问题修复方案

问题概述

开发React笔记应用时,NoteState.js中的MyModal组件存在两个问题:

  • 输入框每次只能输入单个字符,输入后立即失焦,必须重新点击才能继续输入
  • 无法为组件内的3个输入框设置自动聚焦(autoFocus)

相关代码

Notes.js

import React from 'react'
import NoteState from '../context/notes/NoteState'

export const NoteContext = React.createContext()

export default function About() {
  const host = "http://localhost:5000/notes"

  const notesInitial = []

  const [notes, setNotes] = React.useState(notesInitial);

  // 获取所有笔记
  const getNotes = async () => {
    // API调用
    const response = await fetch(`${host}/allnotes`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "auth-token": 'randomgibberish'
      }
    })
      const json = await response.json();
      console.log(json)
      setNotes(json)
    }

  // 添加笔记
  const addNote = async (title, description, tag) => {
    // API调用
    const response = await fetch(`${host}/addnote`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "auth-token": 'randomgibberish'
      },
      body: JSON.stringify({title, description, tag})
    });
    const json = response.json()
    console.log(json);
    
    // 本地添加笔记逻辑
    const note = 
    {
        "_id": "64073219511b4f062e42bb91d1",
        "user": "63fe0f8bbea2272fc98d6e2e",
        "title": title,
        "description": description,
        "tag": tag,
        "Date": "2021-09-03T14:20:09.668Z",
        __v: 0
      };
    setNotes(notes.concat(note))
    }

  // 删除笔记
  const deleteNote = async (id) => {
    // API调用
    const response = await fetch(`${host}/deletenote/${id}`, {
      method: "DELETE",
      headers: {
        "Content-Type": "application/json",
        "auth-token": 'randomgibberish'
      },
      body: JSON.stringify({id})
    })
    const json =  response.json();
    console.log(json)

    // 本地删除笔记逻辑
    console.log("删除ID为" + id + "的笔记")
    const newNotes = notes.filter((note) => { return note._id !== id })
    setNotes(newNotes)
  }

  // 编辑笔记
  const editNote = async (id, title, description, tag) => {
    // API调用
    const response = await fetch(`${host}/updatenote/${id}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "auth-token": 'randomgibberish'
      },
      body: JSON.stringify({title, description, tag})
    })
    const json =  response.json();
    console.log(json);

    // 本地编辑笔记逻辑
    for (let index = 0; index < notes.length; index++) {
      const element = notes[index];
      if (element._id === id) {
        element.title = title;
        element.description = description;
        element.tag = tag;
      }
    }
  }

  return (
    <div>
      <NoteContext.Provider value={{ notes, addNote , deleteNote, editNote, getNotes}}>
        <NoteState />
      </NoteContext.Provider>
    </div>
  )
}

NoteState.js

import React, {useState} from 'react'
import Navbar from '../../components/Navbar';
import { NoteContext} from '../../components/Notes'
import { useEffect } from 'react';
import Noteitem from '../../components/Noteitem'
import AddNote from './AddNote';

export default function NoteState() {
  const context = React.useContext(NoteContext);
  const {notes, getNotes} = context;

  useEffect(() => {
    getNotes()
    // eslint-disable-next-line
}, [])

  const [modal, setModal] = useState(false);
  const [note, setNote] = useState({ etitle: "", edescription: "", etag: "" })
  
  const updateNote = (currentNote) => {
    setNote({etitle: currentNote.title, edescription: currentNote.description, etag:currentNote.tag})
    setModal(true)
  }

  const removenote = () => {
    setModal(false);
  }

  const handleClick = (e) => {
    console.log("更新笔记中...", note);
    e.preventDefault();
    removenote()
  }

  const onChange = (e) => {
    setNote({ ...note, [e.target.name]: e.target.value })
  }

  const MyModal = () => {
    useEffect(() => {
      document.body.style.overflowY="hidden";
      return () => {document.body.style.overflowY="scroll";}
    }, [])
    
      return (
        <>
          <div className="Modal">
              <h3>编辑笔记</h3>
              <input id="etitle" type="text" name='etitle' value={note.etitle} onChange={onChange}/>
              <input id="edescription" type="text" name='edescription' value={note.edescription} onChange={onChange}/>
              <input id="etag" type="text" name='etag' value={note.etag} onChange={onChange}/>
              <div className='edit-btn-cont'>
                  <button id="edit-save" className="edit-btn" onClick={handleClick}>保存更改</button>
                  <button className="edit-btn edit-btn-cancel " onClick={removenote} >取消</button>
              </div>
          </div>
        <div className='modal-container'></div>
          </>
      )
  }

  return (
    <>
      <Navbar />
      <AddNote note={note}/>
      {modal && <MyModal/>}
      <h1 className='yournotes'>你的笔记</h1>
      <div className='notes-box'>
        {notes.map((note) => {
          return <Noteitem key={note._id} updateNote={updateNote} note={note}/>
        })}
        </div>
    </>
  )
}

AddNote.js

import React, { useState } from 'react'
import { NoteContext } from '../../components/Notes'

export default function AddNote() {
  const context = React.useContext(NoteContext);
  const { addNote } = context;

  const [note, setNote] = useState({ title: "", description: "", tag: "默认" })

  const handleClick = (e) => {
    e.preventDefault();
    addNote(note.title, note.description, note.tag);
  }

  const onChange = (e) => {
    setNote({ ...note, [e.target.name]: e.target.value })
  }
  return (
    <div className="Notescontainer">
      <div className='addnoteContainer'>
        <div style={{ display: 'flex', flexDirection: "row" }}>
          <h1 style={{ color: "#8ae595", stroke: "none", textShadow: "#000 1px 1px 0.3px" }}>添加</h1>
          <h1 style={{ marginLeft: "9px", borderBottom: "3px dashed #8ae595", textShadow: "#8ae595 1px 1px 0.3px" }}>笔记</h1>
        </div>
        <input type="text" name='title' id="Notes-textarea" style={{ marginBottom: "7px" }} placeholder='输入标题' onChange={onChange} />
        <textarea name="description" id="Notes-textarea" cols="40" rows="5" placeholder='输入要添加的笔记内容' onChange={onChange}></textarea>
        <div style={{ display: "flex", width: "100%", justifyContent: "center" }}>
          <input type="text" name='tag' className='Notes-input' placeholder='输入标签' style={{ marginTop: "10px", width: "70%", marginRight: "4.55%", padding: "6px" }} onChange={onChange} />
          <button type="submit" className='signup-btn' onClick={handleClick}>提交</button>
          </div>
      </div>
    </div>
  )}

问题修复方案

1. 解决输入框失焦问题

原因:MyModal组件定义在NoteState组件内部,每次NoteState的状态(比如note)更新时,MyModal都会被重新创建,导致DOM节点销毁重建,输入框因此失焦。

修复:将MyModal提取为独立组件,通过props传递所需的状态和方法,避免每次状态更新都重建组件。

2. 解决自动聚焦问题

原因:组件频繁重建导致autoFocus无法稳定生效;同时需要在模态框挂载后主动触发聚焦操作。

修复:在MyModal组件中使用useRef获取输入框DOM元素,配合useEffect在组件挂载后执行聚焦。

修改后的完整NoteState.js

import React, { useState, useEffect, useRef } from 'react'
import Navbar from '../../components/Navbar';
import { NoteContext} from '../../components/Notes'
import Noteitem from '../../components/Noteitem'
import AddNote from './AddNote';

// 提取MyModal为独立组件
const MyModal = ({ note, onChange, handleClick, removenote }) => {
  const titleInputRef = useRef(null);

  useEffect(() => {
    document.body.style.overflowY = "hidden";
    // 模态框挂载后聚焦到标题输入框
    titleInputRef.current?.focus();
    return () => {
      document.body.style.overflowY = "scroll";
    };
  }, []);
  
  return (
    <>
      <div className="Modal">
          <h3>编辑笔记</h3>
          <input 
            ref={titleInputRef}
            id="etitle" 
            type="text" 
            name='etitle' 
            value={note.etitle} 
            onChange={onChange}
          />
          <input 
            id="edescription" 
            type="text" 
            name='edescription' 
            value={note.edescription} 
            onChange={onChange}
          />
          <input 
            id="etag" 
            type="text" 
            name='etag' 
            value={note.etag} 
            onChange={onChange}
          />
          <div className='edit-btn-cont'>
              <button id="edit-save" className="edit-btn" onClick={handleClick}>保存更改</button>
              <button className="edit-btn edit-btn-cancel " onClick={removenote} >取消</button>
          </div>
      </div>
    <div className='modal-container'></div>
      </>
  )
}

export default function NoteState() {
  const context = React.useContext(NoteContext);
  const {notes, getNotes} = context;

  useEffect(() => {
    getNotes()
    // eslint-disable-next-line
}, [])

  const [modal, setModal] = useState(false);
  const [note, setNote] = useState({ etitle: "", edescription: "", etag: "" })
  
  const updateNote = (currentNote) => {
    setNote({etitle: currentNote.title, edescription: currentNote.description, etag:currentNote.tag})
    setModal(true)
  }

  const removenote = () => {
    setModal(false);
  }

  const handleClick = (e) => {
    console.log("更新笔记中...", note);
    e.preventDefault();
    removenote()
  }

  const onChange = (e) => {
    setNote({ ...note, [e.target.name]: e.target.value })
  }

  return (
    <>
      <Navbar />
      <AddNote note={note}/>
      {modal && <MyModal 
        note={note} 
        onChange={onChange} 
        handleClick={handleClick} 
        removenote={removenote} 
      />}
      <h1 className='yournotes'>你的笔记</h1>
      <div className='notes-box'>
        {notes.map((note) => {
          return <Noteitem key={note._id} updateNote={updateNote} note={note}/>
        })}
        </div>
    </>
  )
}

额外优化提示

  • 编辑笔记的本地逻辑中,直接修改原数组元素不符合React状态更新的不可变原则,建议使用map生成新数组后调用setNotes:
// 替换原编辑逻辑
const updatedNotes = notes.map(item => 
  item._id === id ? {...item, title, description, tag} : item
);
setNotes(updatedNotes);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 07:37:02