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
相关产品推荐
相关产品推荐

