React中使用useRef()验证CardElement非空的客户端实现问题
问题
我们在注册表单中使用Stripe的CardElement收集支付信息,需要验证该元素是否填写完整。现有参考方案采用document.querySelector()而非useRef()钩子,尝试通过React.forwardRef()将ref传递给封装后的CardElement时,遇到以下警告:
警告:无法为函数组件提供refs。尝试访问此ref将失败。是否要使用React.forwardRef()?
同时,通过ref获取类名判断填写状态的方式也无效。现需解决两个问题:
- 如何使用
useRef()获取CardElement的类名来验证? - 是否存在无需获取类名的其他验证方式?
相关代码示例
// 封装的CardElement组件 const OurCardElement = React.forwardRef((props, ref) => { return ( <OurElementContainer> <CardElement ref={ref} isDisabled={true} options={cardElementOpts} /> </OurElementContainer> ); }); // 注册页面组件 const SignupPage = () => { const stripe = useStripe(); const elements = useElements(); const cardElementRef = useRef(); // 尝试判断状态但无效 let isCardComplete = ref.current.className.includes('StripeElement--complete') const handleSignup = async (e) => { const cardElement = elements.getElement('card'); e.preventDefault(); try { cardElement.update({ disabled: true }); const paymentMethodReq = await stripe.createPaymentMethod({ type: 'card', card: cardElement }); const paymentObject = { id, paymentMethod: paymentMethodReq.paymentMethod.id }; const stripeResponse = await Axios.post(`${apiBaseUrl}/stripe/new_payment_subscriptions`, paymentObject); } catch (err) { console.log(err); } }; return ( <>... 注册表单其他内容(姓名、邮箱、密码等) <OurCardElement ref={cardElementRef} /> <button onClick={() => handleSignup} isDisabled={!isCardComplete} /> </> ); });
解决方案
一、正确使用useRef获取CardElement类名
封装组件虽已使用forwardRef,但Stripe的CardElement是受控组件,直接传递ref无法获取DOM元素。需调整ref的绑定逻辑,通过容器ref间接获取CardElement的DOM节点:
const OurCardElement = React.forwardRef((props, ref) => { const containerRef = useRef(); useEffect(() => { if (containerRef.current) { // 将CardElement的DOM节点暴露给外部ref ref.current = containerRef.current.querySelector('.StripeElement'); } }, [ref]); return ( <OurElementContainer ref={containerRef}> <CardElement isDisabled={true} options={cardElementOpts} /> </OurElementContainer> ); });
在注册页面,通过监听CardElement的change事件更新完成状态:
const SignupPage = () => { const stripe = useStripe(); const elements = useElements(); const cardElementRef = useRef(); const [isCardComplete, setIsCardComplete] = useState(false); useEffect(() => { const checkCardStatus = () => { if (cardElementRef.current) { setIsCardComplete(cardElementRef.current.classList.contains('StripeElement--complete')); } }; const cardElement = elements.getElement('card'); if (cardElement) { // 监听输入变化 cardElement.on('change', checkCardStatus); // 初始化时检查一次 checkCardStatus(); // 组件卸载时移除监听 return () => cardElement.off('change', checkCardStatus); } }, [elements]); // ... 其他代码保持不变 return ( <> {/* 注册表单其他内容 */} <OurCardElement ref={cardElementRef} /> <button onClick={handleSignup} disabled={!isCardComplete} >提交</button> </> ); };
二、推荐:使用Stripe官方内置验证(无需操作类名)
Stripe Elements提供了change事件,可直接获取卡片填写的complete状态,这是更可靠、符合React最佳实践的方式:
const SignupPage = () => { const stripe = useStripe(); const elements = useElements(); const [isCardComplete, setIsCardComplete] = useState(false); useEffect(() => { const cardElement = elements.getElement('card'); if (!cardElement) return; const handleCardChange = (event) => { // 直接从事件对象获取完成状态 setIsCardComplete(event.complete); // 可同时处理错误提示 if (event.error) { console.log('卡片输入错误:', event.error.message); } }; cardElement.on('change', handleCardChange); return () => cardElement.off('change', handleCardChange); }, [elements]); const handleSignup = async (e) => { e.preventDefault(); if (!stripe || !elements) return; const cardElement = elements.getElement('card'); try { cardElement.update({ disabled: true }); const { error, paymentMethod } = await stripe.createPaymentMethod({ type: 'card', card: cardElement, }); if (error) { console.log(error.message); cardElement.update({ disabled: false }); return; } const paymentObject = { id: '替换为实际用户ID', paymentMethod: paymentMethod.id }; await Axios.post(`${apiBaseUrl}/stripe/new_payment_subscriptions`, paymentObject); } catch (err) { console.log(err); cardElement.update({ disabled: false }); } }; return ( <> {/* 注册表单其他内容 */} <OurCardElement /> <button onClick={handleSignup} disabled={!isCardComplete} >提交</button> </> ); };
这种方式无需操作DOM类名,完全依赖Stripe提供的API获取状态,稳定性更高。
内容的提问来源于stack exchange,提问作者Canovice
相关产品推荐
相关产品推荐

