React+Antd项目中MaskedInput输入框值丢失问题求助
问题解决:React + Antd 表单手机号输入框值丢失问题
问题根源
antd-mask-input与Antd Form绑定不完整:默认情况下MaskedInput没有自动适配Antd Form的状态管理机制,组件重新渲染(头像上传后状态更新、切标签页激活)时,无法从Form的状态仓库中正确读取并展示值。useEffect依赖数组不规范:原代码中useEffect依赖数组为空,但内部使用了oneTime和axiosPrivate,可能导致状态不同步或重复执行问题。- 头像缓存问题:上传新头像后,浏览器可能缓存旧图片,同时状态更新可能间接触发组件渲染异常。
解决方案
1. 手动绑定MaskedInput与Form状态
通过Form.useWatch获取表单字段值,手动实现value和onChange的双向绑定,确保输入值始终同步到Form的状态仓库:
// 引入Form的useWatch方法 const { useForm, useWatch } = Form; // 在组件内部添加监听 const phoneNumber = useWatch('phoneNumber', form); // 修改手机号输入框的Form.Item <Form.Item name="phoneNumber" label="phoneNumber"> <MaskedInput mask="(000) 000 00 00" placeholder="phoneNumber" value={phoneNumber || ''} onChange={(e) => form.setFieldValue('phoneNumber', e.target.value)} /> </Form.Item>
2. 规范useEffect依赖数组
将oneTime、axiosPrivate和form添加到依赖数组中,避免React状态不同步:
useEffect(() => { if (oneTime) { // 原数据加载逻辑 } }, [oneTime, axiosPrivate, form]);
3. 解决头像缓存与上传状态问题
给头像URL添加时间戳,强制浏览器加载新图片;同时完善上传失败时的状态重置:
// 上传成功后更新头像地址 setImageUrl(`http://localhost:3001/avatars/avatar_${id}.jpg?t=${Date.now()}`); // 上传失败时重置loading状态 .catch((err) => { onError({ event: err }); setLoading(false); });
完整修改后的代码
import React, { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import useAxiosPrivate from "../hooks/useAxiosPrivate"; import { Alert, Form, Button, Input, notification, Space, Upload } from "antd"; import { MaskedInput } from "antd-mask-input"; import { LoadingOutlined, PlusOutlined } from "@ant-design/icons"; function Soru() { let { id } = useParams(); const [form] = Form.useForm(); const { useWatch } = Form; const phoneNumber = useWatch('phoneNumber', form); const axiosPrivate = useAxiosPrivate(); const [isPending, setIsPending] = useState(false); const [errMessage, setErrMessage] = useState(null); const [oneTime, setOneTime] = useState(true); const [api, contextHolder] = notification.useNotification(); const [loading, setLoading] = useState(false); const [imageUrl, setImageUrl] = useState(); const handleChange = (info) => { setLoading(info.file.status === "uploading"); }; const customUpload = async ({ onError, onSuccess, file }) => { let formData = new FormData(); formData.append("avatar", file); const config = { headers: { "Content-Type": "multipart/form-data" } }; axiosPrivate .post(`http://localhost:3001/updateuser/${id}`, formData, config) .then((res) => { onSuccess(true); setLoading(false); setIsPending(false); setImageUrl(`http://localhost:3001/avatars/avatar_${id}.jpg?t=${Date.now()}`); }) .catch((err) => { onError({ event: err }); setLoading(false); }); }; useEffect(() => { if (oneTime) { console.log("only one time"); axiosPrivate .get(`http://localhost:3001/users/byId/${id}`) .then((response) => { if (response.statusText !== "OK") { const error = response.data.message; setErrMessage("response : " + error.name); setIsPending(false); return Promise.reject(error); } else { setIsPending(false); form.setFieldsValue({ phoneNumber: response.data.phoneNumber, firstName: response.data.firstName, lastName: response.data.lastName, }); setImageUrl(`http://localhost:3001/avatars/avatar_${id}.jpg?t=${Date.now()}`); setOneTime(false); } }) .catch((err) => { setIsPending(false); console.log("err resp : " + err); }); } }, [oneTime, axiosPrivate, form]); const onSubmit = (e) => { setIsPending(true); axiosPrivate .put(`http://localhost:3001/users/byId/${id}`, e) .then((response) => { setIsPending(false); api["success"]({ message: "saved", placement: "bottomRight" }); }) .catch((err) => { setIsPending(false); api["error"]({ description: err.message || '保存失败', placement: "bottomRight" }); }); }; return ( <Form form={form} autoComplete="off" labelCol={{ span: 10 }} wrapperCol={{ span: 8 }} onFinish={(values) => { onSubmit(values); }} onFinishFailed={(error) => { console.log("onFinishFailed : ", error); }} > {contextHolder} <Space direction="horizontal" style={{ width: "100%", justifyContent: "center" }}> <Upload name="avatar" listType="picture-circle" className="avatar-uploader" showUploadList={false} customRequest={customUpload} onChange={handleChange} > {imageUrl ? ( <img src={imageUrl} alt="avatar" style={{ width: "100%", borderRadius: "50%"}} /> ) : ( <button style={{ border: '1px solid', background: "none" }} type="button"> {loading ? <LoadingOutlined style={{ cursor: "pointer" }} /> : <PlusOutlined style={{ cursor: "pointer" }} />} <div style={{ marginTop: 8, marginBottom: -20, cursor: "pointer" }}>Upload</div> </button> )} </Upload> </Space> <Form.Item name="phoneNumber" label="phoneNumber"> <MaskedInput mask="(000) 000 00 00" placeholder="phoneNumber" value={phoneNumber || ''} onChange={(e) => form.setFieldValue('phoneNumber', e.target.value)} /> </Form.Item> <Form.Item name="firstName" label="firstName" rules={[{ required: true }, { whitespace: true }]}> <Input placeholder="firstName" maxLength={20} onInput={(e) => { e.target.value = e.target.value.charAt(0).toUpperCase() + e.target.value.slice(1); }} /> </Form.Item> <Form.Item name="lastName" label="lastName" rules={[{ required: true }, { whitespace: true }]}> <Input placeholder="lastName" onInput={(e) => (e.target.value = e.target.value.toUpperCase())} maxLength={20} /> </Form.Item> <Space direction="horizontal" style={{ width: "100%", justifyContent: "center" }}> <Form.Item wrapperCol={{ span: 24 }}> <Button block type="primary" htmlType="submit" loading={isPending} danger={errMessage}> Save </Button> </Form.Item> </Space> {errMessage && ( <Space direction="horizontal" style={{ width: "100%", justifyContent: "center" }}> <Alert type="error" message={errMessage} /> </Space> )} </Form> ); } export default Soru;
内容的提问来源于stack exchange,提问作者Ufuk Ugur
相关产品推荐
相关产品推荐

