如何在表单提交函数中使用useAsync hook解决Hook规则调用报错
问题原因
- 所有React Hook只能在组件顶层作用域或者自定义Hook内部调用,你在事件处理函数
handleSubmit里调用useAsync、useFetch两个Hook,直接违反Hook规则,所以触发报错。 - 尝试把
onSubmit改为首字母大写的做法完全不对:首字母大写的函数在React中会被识别为组件,事件处理函数是普通回调,不需要也不能改成大写命名,所以会触发运行时错误。 react-async提供的useAsync默认是组件渲染时自动执行异步逻辑,不适合手动触发的表单提交场景,你需要用同库提供的useAsyncFn,它会返回手动触发执行的run方法,符合你的需求。
解决步骤
- 先把原本封装在
useFetchHook里的请求逻辑抽成普通的返回Promise的工具函数,不要用Hook封装 - 在组件顶层调用
useAsyncFn,传入promiseFn定义请求逻辑,接收返回的执行函数和状态 - 在
handleSubmit中调用返回的执行函数,传入表单参数触发请求
修改后代码
import { Modal } from 'react-bootstrap'; import "react-datepicker/dist/react-datepicker.css"; import DatePicker from 'react-datepicker' import { useState } from 'react'; // 导入useAsyncFn替代useAsync import { useAsyncFn } from 'react-async'; import { useLocation } from 'react-router-dom'; // 抽离为普通请求工具函数,替换原来的useFetch自定义Hook const request = async (url: string, method: string, data: any) => { const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) if (!res.ok) throw new Error('请求失败') return res.json() } const TodoPopup = (props : {show : boolean, onHide : () => void, title : string, values? : {title : string, expirationDate : any, description : string}}) => { const [name, setName] = useState(props.values?.title || ''); const [date, setDate] = useState(props.values?.expirationDate || ''); const [description, setDescription] = useState(props.values?.description || ''); const url = useLocation().pathname; // 组件顶层调用useAsyncFn,定义请求逻辑 const [{ data, error, isPending }, runRequest] = useAsyncFn(async (name: string, date: Date, description: string) => { const method = props.values ? 'PUT' : 'POST' return request(url, method, {name, date, description}) }, [url, props.values]) // 声明依赖项 const handleSubmit = (e : React.FormEvent<HTMLFormElement>) => { e.preventDefault(); // 手动触发提交请求 runRequest(name, date, description) } // 处理请求状态 if(isPending) console.log('loading...'); if(error) console.log('error'); if(data) props.onHide(); return ( <Modal {...props} centered> <Modal.Header closeButton> <Modal.Title>{props.title}</Modal.Title> </Modal.Header> <form onSubmit={handleSubmit}> <Modal.Body> <div className = "form-group"> <label>Task Name</label> <input type="text" className = "form-control" value = {name} onChange = {(e) => setName(e.target.value)} required /> </div> <div className = "form-group"> <label>Expired</label> <DatePicker selected={date} onChange={e => setDate(e)} className="form-control" minDate={new Date()} required /> </div> <div className = "form-group"> <label>Description</label> <textarea rows = {5} className = "form-control" value = {description} onChange = {(e) => setDescription(e.target.value)}></textarea> </div> </Modal.Body> <Modal.Footer> <button className="btn btn-primary" type="submit">Save changes</button> </Modal.Footer> </form> </Modal> ); }; export default TodoPopup;
内容的提问来源于stack exchange,提问作者shalev sror
相关产品推荐
相关产品推荐

