
本文介绍在 React 应用中使用 react-otp-input 时,如何仅在 UI 上将已输入的 OTP 数字替换为 * 显示,同时完整保留原始 OTP 值用于后端校验,避免因状态被覆盖导致登录失败。
本文介绍在 react 应用中使用 `react-otp-input` 时,如何仅在 ui 上将已输入的 otp 数字替换为 `*` 显示,同时完整保留原始 otp 值用于后端校验,避免因状态被覆盖导致登录失败。
在实现 OTP 登录功能时,一个常见需求是提升用户隐私体验:当用户输入 4 位数字后,UI 上短暂显示明文,0.5 秒后自动隐藏为 ****。但若直接修改存储 OTP 的状态(如 setOTP('****')),会导致后续提交的 OTP 值变为掩码字符串,与服务端生成/存储的真实 OTP 不匹配,从而登录失败。
根本解法是分离「展示状态」与「业务状态」:
- OTP(业务状态):始终保存用户真实输入的纯数字字符串(如 '1234'),仅用于接口请求和逻辑校验;
- maskedOTP(展示状态):仅用于
的 value 属性,受 OTP 驱动,延迟更新为掩码形式(如 '****'),不影响业务逻辑。
以下是推荐实现:
构建 Shopify 嵌入式应用(Embedded App)的开发技能:基于 @shopify/shopify-app-react-router + React Router v7 + Admin API 2026-04。覆盖 shopifyApp 配置、authenticate.admin 的 GraphQL/REST 调用、Webhook 校验、Session 存储、订阅计费、App Bridge 导航、部署,并新增:适用场景判断、安全最佳实践、错误处理与容错、最佳实践、常见问题 FAQ、高级场景。G
const [OTP, setOTP] = useState(''); // ✅ 真实 OTP,用于登录请求
const [maskedOTP, setMaskedOTP] = useState(''); // ✅ 仅用于 UI 渲染
// 监听 OTP 变化,500ms 后更新掩码显示
useEffect(() => {
if (OTP.length === 4) {
const timer = setTimeout(() => {
setMaskedOTP(OTP.replace(/\d/g, '*')); // 将每位数字转为 '*'
}, 500);
return () => clearTimeout(timer);
} else {
setMaskedOTP(OTP); // 输入未满 4 位时,保持明文显示(可选)
}
}, [OTP]);
const onOtpChange = (value) => {
// ✅ 关键:只更新业务状态 OTP,不触发展示逻辑
setOTP(value);
if (value.length === 4) {
// ✅ 提交时使用原始 value(即真实的 OTP 字符串)
props.login({
username: auth.get('userName'),
password: auth.get('password'),
otp: value, // ← 这里必须是 '1234',而非 '****'
type: "WEB",
serial: window.localStorage.getItem("visitorId"),
showProfileWizard: true,
showEftDialog: true,
});
props.resetResendOtp();
} else if (value.length }
inputStyle={{
width: mobileWidth || tabletWidth ? 40 : 60,
height: mobileWidth || tabletWidth ? 40 : 60,
backgroundColor: '#EDEDEC',
border: 'none',
borderRadius: 20,
}}
focusStyle={{
borderRadius: 20,
border: `2px solid ${theme.palette.primary.main}`,
outline: 'none',
}}
/>
注意事项:
- useEffect 中添加 OTP.length === 4 判断,避免在输入过程中(如 '1'、'12')过早触发掩码,影响用户体验;
- 若需支持“输入中实时掩码”(如每输一位立即变 *),可移除长度判断,但建议保留 0.5s 延迟以兼顾可读性与隐私;
- onOtpChange 中调用 props.login 时务必传入 value 参数(原始输入),而非 OTP 状态——虽然二者当前一致,但语义更清晰且规避潜在竞态风险;
- maskedOTP 初始值设为空字符串,确保首次渲染无异常。
通过状态职责分离,既满足了 UI 层的隐私掩码需求,又保障了业务层 OTP 值的完整性与准确性,是 React 中处理“展示 vs 数据”差异的经典实践。










