Next.js添加保存逻辑后Input的onChange事件未触发问题
问题描述
在Next.js项目中,添加文本保存逻辑后,Input的onChange事件不再触发;注释掉保存逻辑后功能恢复正常。当前需要实现区块文本保存与图片上传功能:文本可通过handleClickOutside函数正常保存,但图片选择后无法显示,控制台排查发现handleImageUpload函数未被触发(对应代码:<input type="file" className='hidden' onChange={handleImageUpload} ></input>)。
相关代码
图片上传代码
import {useState,useEffect,useRef} from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import {faCamera} from '@fortawesome/free-solid-svg-icons' export function imageUpload(defaultLanguageCode,index,sectionsArray,setImageData){ const selectedImage=useRef(null); const selectableImage=useRef([]); console.log(defaultLanguageCode); function showUploadImage(event){ event.stopPropagation(); // Stop event propagation selectedImage.current=event.target.parentElement.parentElement.children[1]; event.target.parentElement.parentElement.children[1].click(); } function handleImageUpload(event){ console.log(event.target.files[0]) selectedImage.current.src=URL.createObjectURL(event.target.files[0]); console.log(event.target.files[0].name) const image=event.target.files[0].name; setImageData(image); // const apiUrl = "http://wimdrive-wimapp-service.wimsolutions.net/api/website/wbsWebsiteSection/data/"+(sectionsArray[index]).id+"?key=image&value=" + image + "&code="+defaultLanguageCode; //fetch(apiUrl, { //method: "PUT", //headers: { //"Content-Type": "application/json", // Add any other headers if needed // }, // You can also provide a request body if needed //body: JSON.stringify({}), //}) //.then(response => response.json()) //.then(data => { // Handle API response if needed //console.log("API response:", data); //}) //.catch(error => { // Handle API error //console.error("API error:", error); //}); // console.log("uploaded succcc") } //console.log("yes",imageData) function handleMouseLeave(event){ if(event.target.children[0]) event.target.children[0].classList.add('invisible'); } function handleMouseEnter(event){ if(event.target.children[0]) event.target.children[0].classList.remove('invisible'); } function showUploadButton(){ selectableImage.current.forEach((element)=>{ if(element!=null){ element.addEventListener('mouseenter',handleMouseEnter); element.addEventListener('mouseleave',handleMouseLeave); } }) } function UploadComponent(){ return( <div className="edit-image absolute bg-transparent mt-1 rounded w-full h-full " onClick={showUploadImage} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > <FontAwesomeIcon icon={faCamera} className='pe-3 invisible' style={{fontSize:"40px",color:"black"}} /> <input type="file" className='hidden' onChange={handleImageUpload} ></input> </div> ) } return { UploadComponent, selectableImage, selectedImage, } }
编辑页面元素保存代码
import { imageUpload } from "/helpers/imageUpload"; import { useEffect, useRef, useState } from "react"; export const useEditableElements = (sectionsArray,index,defaultLanguageCode, sectData,sectionIndex, siteInfos, imageData) => { const selectableElement = useRef({}); const [editorPosition, setEditorPosition] = useState({ top: 0, left: 0 }); const [isEditorVisible, setIsEditorVisible] = useState(false); const selectedElement = useRef(null); const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); const [isMouseDown, setIsMouseDown] = useState(false); const [data, setData] = useState(false); const editorRef = useRef(null); // EDITOR RELATED useEffect(() => { }, [defaultLanguageCode,sectData]); useEffect(() => { if (selectableElement) { Object.entries(selectableElement.current).forEach(([name, element]) => { element.addEventListener("click", handleElementClick); element.contentEditable = true; element.classList.add("editable-element"); }); } }, [index]); function handleElementClick(event) { if ( event.target.classList.contains("editable-element") && (selectedElement.current == null || selectedElement.current != event.target) ) { setEditorPosition({ x: event.currentTarget.getBoundingClientRect().left, y: event.currentTarget.getBoundingClientRect().top - event.currentTarget.getBoundingClientRect().height, }); setIsEditorVisible(true); selectedElement.current = event.target; } } useEffect(() => { document.addEventListener("click", handleClickOutside); return () => { document.removeEventListener("click", handleClickOutside); }; }); function saveData(newData) { // Add the imageData to the newData object const newDataWithImage = { ...newData, image: imageData }; setData(newDataWithImage); const a = { ...siteInfos, sectionIndex, section: { ...sectData, data: newDataWithImage, }, }; console.log("newDataWithImage:", newDataWithImage); console.log(defaultLanguageCode); if (sectData !== undefined && sectionsArray !== undefined && sectionsArray[index] !== undefined){ const apiUrl = ("http://wimdrive-wimapp-service.wimsolutions.net/api/website/wbsWebsiteSection/updateData/"+(sectionsArray[index]).id+"?code="+defaultLanguageCode); console.log("apiUrl:", apiUrl); // PUT request headers const headers = { "Content-Type": "application/json", // Set the Content-Type to JSON }; // Convert newData to JSON format const requestBody = JSON.stringify(newDataWithImage); // Perform the PUT request fetch(apiUrl, { method: "PUT", headers: headers, body: requestBody, }) .then(response => { if (!response.ok) { throw new Error("Network response was not ok."); } return response.json(); }) .then(data => { // The API response data will be available here console.log("API response:", data); }) .catch(error => { // Handle any errors that occurred during the API request console.error("Error:", error); }); } } console.log(sectData) function handleClickOutside(event) { event.stopPropagation(); if ( !event.target.classList.contains("editable-element") && !event.target.closest("#my-editor") ) { selectedElement.current = null; const isDataChanged = Object.entries(selectableElement.current).some( ([name, element]) => { if (sectData!== undefined) { return element.innerText !== sectData[name]; } return false; } ); if (isDataChanged) { const newData = Object.entries(selectableElement.current).reduce( (acc, [name, element]) => { acc[name] = element.innerHTML; return acc; }, {} ); saveData(newData); } } } const handleMouseDown = (event) => { setIsMouseDown(true); }; const handleMouseMove = (event) => { if (isMouseDown) { const { top, left, width, height } = editorRef.current.getBoundingClientRect(); setElementPosition({ x: event.clientX - width / 2 + "px", y: event.clientY - height / 2 + "px", }); } }; const handleMouseUp = (event) => { setIsMouseDown(false); }; const resetSelector = () => { selectedElement.current = null; }; return { selectableElement, handleElementClick, isEditorVisible, editorPosition, setIsEditorVisible, selectedElement, resetSelector, }; };
布局代码
return ( <section ref={mySection} class="relative md:py-24 py-16"> <div class="container"> <div class="grid md:grid-cols-12 grid-cols-1 items-center gap-[30px]"> <div class="lg:col-span-5 md:col-span-6 editable-image-container"> <UploadComponent /> <img src= {sectData.image} class="rounded-md shadow dark:shadow-gray-800" alt="defaultimage"/> </div> <div class="lg:col-span-7 md:col-span-6"> <div class="ms-6"> <h4 ref={(ref) => (selectableElement.current["title"] = ref)} class="my-6 md:text-3xl text-2xl md:leading-normal leading-normal font-semibold">{sectData.title}</h4> <p ref={(ref) => (selectableElement.current["paragraph"] = ref)} class="text-slate-400">{sectData.paragraph}.</p> </div> </div> </div> </div> )
解决方案
1. 修复文件Input onChange事件不触发问题
全局绑定的handleClickOutside存在两个问题:无依赖数组导致重复绑定事件,以及事件处理逻辑干扰了文件选择后的事件传递。
修改useEditableElements中的事件监听逻辑:
// 原代码 useEffect(() => { document.addEventListener("click", handleClickOutside); return () => document.removeEventListener("click", handleClickOutside); }); // 修改后:添加空依赖数组,仅绑定一次 useEffect(() => { document.addEventListener("click", handleClickOutside); return () => document.removeEventListener("click", handleClickOutside); }, []);
同时更新handleClickOutside,排除文件上传相关元素:
function handleClickOutside(event) { // 跳过文件上传相关元素的点击事件 if (event.target.type === 'file' || event.target.closest('.edit-image')) { return; } if ( !event.target.classList.contains("editable-element") && !event.target.closest("#my-editor") ) { selectedElement.current = null; const isDataChanged = Object.entries(selectableElement.current).some( ([name, element]) => { if (sectData!== undefined) { return element.innerText !== sectData[name]; } return false; } ); if (isDataChanged) { const newData = Object.entries(selectableElement.current).reduce( (acc, [name, element]) => { acc[name] = element.innerHTML; return acc; }, {} ); saveData(newData); } } }
2. 修复handleImageUpload未触发问题
原代码通过DOM遍历获取input元素的方式极易出错,改用ref直接引用:
修改imageUpload函数:
export function imageUpload(defaultLanguageCode, index, sectionsArray, setImageData) { const selectedImage = useRef(null); const selectableImage = useRef([]); const fileInputRef = useRef(null); // 新增文件input的ref function showUploadImage(event) { event.stopPropagation(); // 直接通过父容器获取img元素 selectedImage.current = event.currentTarget.parentElement.querySelector('img'); // 通过ref触发文件选择框点击 fileInputRef.current?.click(); } // ... 其他函数保持不变 function UploadComponent() { return ( <div className="edit-image absolute bg-transparent mt-1 rounded w-full h-full " onClick={showUploadImage} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > <FontAwesomeIcon icon={faCamera} className='pe-3 invisible' style={{fontSize:"40px",color:"black"}} /> {/* 绑定ref到input */} <input ref={fileInputRef} type="file" className='hidden' onChange={handleImageUpload} /> </div> ) } return { UploadComponent, selectableImage, selectedImage, } }
3. 优化事件重复绑定问题
原代码在index变化时会重复给元素添加点击事件,添加清理逻辑避免重复绑定:
useEffect(() => { if (selectableElement.current) { // 先移除旧事件 Object.entries(selectableElement.current).forEach(([name, element]) => { element.removeEventListener("click", handleElementClick); }); // 再添加新事件 Object.entries(selectableElement.current).forEach(([name, element]) => { element.addEventListener("click", handleElementClick); element.contentEditable = true; element.classList.add("editable-element"); }); } // 清理函数:组件卸载或index变化时移除事件 return () => { if (selectableElement.current) { Object.entries(selectableElement.current).forEach(([name, element]) => { element.removeEventListener("click", handleElementClick); }); } }; }, [index]);
内容的提问来源于stack exchange,提问作者ribert
相关产品推荐
相关产品推荐

