You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Antd项目中MaskedInput输入框值丢失问题求助

问题解决:React + Antd 表单手机号输入框值丢失问题

问题根源

  1. antd-mask-input与Antd Form绑定不完整:默认情况下MaskedInput没有自动适配Antd Form的状态管理机制,组件重新渲染(头像上传后状态更新、切标签页激活)时,无法从Form的状态仓库中正确读取并展示值。
  2. useEffect依赖数组不规范:原代码中useEffect依赖数组为空,但内部使用了oneTime和axiosPrivate,可能导致状态不同步或重复执行问题。
  3. 头像缓存问题:上传新头像后,浏览器可能缓存旧图片,同时状态更新可能间接触发组件渲染异常。

解决方案

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:40:57