React自定义钩子调用无效错误求助及代码排查
解决React钩子调用错误:Invalid hook call
错误核心原因
你触发错误的直接原因是违反了React Hook的调用规则:在组件的handleSubmit事件回调内部调用了useStorage和useUpdateUser钩子。React要求所有钩子必须直接在函数组件的顶层作用域调用,禁止在事件处理函数、循环、条件判断或嵌套函数内调用。
分步解决方案
1. 重构钩子调用位置
将useStorage和useUpdateUser移到组件顶层,通过状态变化触发逻辑,而非在提交函数内调用:
修改ProfileDataForm.jsx:
const ProfileDataForm = () =>{ const [formValues , setFormValues] = useState(userExample); // 把useStorage移到组件顶层,依赖formValues中的文件状态 const {progress: progressPhoto, url: urlPhoto, error: errorPhoto} = useStorage(formValues.photo, "ejem.jpeg", "images/userPhotos"); const {progress: progressPdf, url: urlPdf, error: errorPdf} = useStorage(formValues.inscriptionComprobant, "ejem.pdf", "pdfs/inscriptionComprobant"); // 重构useUpdateUser,返回可执行的更新函数 const [newDoc, updateError, updateUser] = useUpdateUser(); const handleSubmit = async (event) => { event.preventDefault(); const uid = "e9O4OfTsNRfPITvtcsF97YgMaYT2"; // 等待文件上传完成,获取到URL后执行更新 if(urlPhoto && urlPdf && !errorPhoto && !errorPdf){ await updateUser(uid, { ...formValues, photo: urlPhoto, inscriptionComprobant: urlPdf }); // 根据更新结果调整状态 // setFormValues(updatedDoc); } } // 其余代码保持不变 }
2. 修复uploadFile参数顺序错误
你的useStorage中调用uploadFile(file, fileName, folderPath),但uploadFile的定义参数顺序是(folderPath, fileName, file),完全颠倒会导致上传路径错误:
修改uploadFile.js:
export const uploadFile = (file, fileName, folderPath) =>{ // 路径添加斜杠分隔,避免拼接错误 const storageRef = ref(storage, `${folderPath}/${fileName}`); const uploadTask = uploadBytesResumable(storageRef, file); return uploadTask; }
3. 重构useUpdateUser钩子
useUpdateUser同样不能在回调内调用,需改成返回可执行的更新函数:
示例重构后的useUpdateUser:
export function useUpdateUser(){ const [newDoc, setNewDoc] = useState(null); const [updateError, setUpdateError] = useState(null); const updateUser = async (uid, data) => { try { const doc = await updateDocument(uid, data); setNewDoc(doc); setUpdateError(null); return doc; } catch (err) { setUpdateError(err); throw err; } } return [newDoc, updateError, updateUser]; }
4. 优化useStorage的上传状态管理(可选)
添加上传状态和清理逻辑,提升用户体验和代码健壮性:
修改useStorage.js:
export function useStorage(file, fileName, folderPath){ const [progress, setProgress] = useState(0); const [error, setError] = useState(null); const [url, setUrl] = useState(null); const [isUploading, setIsUploading] = useState(false); useEffect(() => { if (!file) return; setIsUploading(true); setError(null); setUrl(null); const uploadTask = uploadFile(file, fileName, folderPath) .on("state_changed", (snapshot) => { const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100); setProgress(progress); }, (error) => { setError(error); setIsUploading(false); }, async () => { try { // 注意:使用当前uploadTask的snapshot,而非uploadFile函数 const downloadURL = await getFileUrl(uploadTask.snapshot.ref); setUrl(downloadURL); } catch (err) { setError(err); } finally { setIsUploading(false); } } ); // 组件卸载时取消上传 return () => uploadTask.cancel(); }, [file, fileName, folderPath]); return { progress, url, error, isUploading }; };
额外检查项
- 检查React版本一致性:执行
npm ls react react-dom,确保两者版本完全匹配 - 移除重复React副本:若依赖树中存在多个React版本,删除
node_modules和package-lock.json后重新安装依赖
内容的提问来源于stack exchange,提问作者Juan Pablo SR
相关产品推荐
相关产品推荐

