Feature request or idea? Consider opening an API review!
Thank you very much for maintaining this fantastic library.
When a user navigates to my payment page, I would like to be able to auto-focus on the card number element - however, the library does not seem to support
<CardNumberElement autoFocus />
nor
<CardNumberElement autoFocus={true} />
Could that be added?
@darrenklein thanks for opening this issue! It's certainly easy to imagine cases where autoFocus would help increase conversions on a checkout form.
Are you interested in writing a PR for this? The underlying non-React Elements API already supports element.focus() so it should just be a question of calling that from the right place in the lifecycle of the React Element component.
Hey @darrenklein we discussed this a bit on the team and there are a couple complications, especially around some upcoming work we're doing to make it possible to load the stripe.js code asynchronously. The net result would be that autoFocus would behave unpredictably, running in a different order on different page executions and possibly leading to unpredictable behavior if you use focus elsewhere on your page. So I don't think adding this to the library's API surface area is something we want to support.
The good news is, it's pretty easy to do this yourself inside whatever of your components is rendering the <Element>. Just use a React ref to get a handler to that element instance and then access the underlying stateful element and use its .focus() method. It _might_ look something like this (though I haven't tried this code and probably have some typos):
class Checkout extends React.Component {
render() {
return (
<div>
<CardElement ref={(instance) => { instance._element.focus(); }} />
</div>
);
}
}
Though we would caution you: we've seen auto-focus, especially when it eventually focuses inside an iframe, cause problems on Mobile Safari and Mobile Chrome. Things like the keyboard failing to open or the previously-focused item on the parent document failing to fire blur events. So be sure to test if you do this!
Great, thanks so much, @asolove-stripe. I was excited at the prospect of opening a PR, but it wouldn't make sense, given what your team is working on. Good call on the warnings about auto-focus - if I use your method, I'll test carefully.
@asolove-stripe quick question - when the Stripe elements load in the page, is there any sort of callback that can be listened for? Here's the situation...
I was able to implement the .focus() behavior with a variation of what you suggested, but to get it to work, I actually had to use setTimeout() - here's the component:
import React, { Component } from "react"
import { CardNumberElement } from "react-stripe-elements"
class CardNumber extends Component {
render() {
return (
<div className="CardNumber FormInput">
<label>
Card number
<CardNumberElement ref={(instance) => { if (instance) { setTimeout(function() {instance._element.focus()}, 300) } }} />
</label>
</div>
)
}
}
export default CardNumber
First off, note that I needed the conditional if (instance) in the element's ref - when the page loads, instance would be defined, then briefly return undefined before becoming defined again. Ok, no big deal. However, I tried to call instance._element.focus() there, it had no affect on the UI, the field did not focus. After some testing, I found that if I could implement a short delay, it would work - 300 milliseconds seemed to be the threshold - any less, and it won't work. I assume that this has something to do with the time needed to load the Stripe content? I don't like using timed delays for async stuff, though, I'd much prefer to hook into a callback - are there any specific events or conditions that I can listen for to trigger this code? Standard React lifecycle functions all ran too quickly...
Hmm, the ref going undefined for a second doesn't make a lot of sense to me. Is it possible this component is being unmounted briefly? If not, sounds like a bug in this library that we should fix.
As for knowing when the element can accept commands: yes! Once you have a reference to the wrapped _element, you can do element.on('ready', ...).
@asolove-stripe yes! That works - here's the code I used:
<CardNumberElement ref={(instance) => { if (instance) { instance._element.on("ready", () => { instance._element.focus() }) } }} />
To the bit about the ref going undefined - I double-checked, it actually goes null. I tested componentWillUnmount() and it was not triggered, so it seems that the component was not unmounting.
Ok, thanks for looking, I'll open a separate issue to track that down.
Oh, you know, I think this is a React thing. Whenever the component is re-rendered, React will look at the ref callback. If it isn't === to the old one, it calls the old one with null and then the new one with the instance.
In this code, you are creating a new instance of the ref function every time render is called. Can you try extracting that function into a module variable so that you use the exact same instance on every render? Then the null call and third call should go away.
@asolove-stripe Perfect! You're right, this is a React feature - for reference, see the 'Caveats' section: https://reactjs.org/docs/refs-and-the-dom.html
My 'perfect' component is ready - here it is:
import React, { Component } from "react"
import { CardNumberElement } from "react-stripe-elements"
const setRef = (instance) => {
if (instance) {
instance._element.on("ready", () => {
instance._element.focus()
})
}
}
class CardNumber extends Component {
render() {
return (
<div className="CardNumber FormInput">
<label>
Card number
<CardNumberElement ref={setRef} />
</label>
</div>
)
}
}
export default CardNumber
It should be noted that I've tested this component on desktop Chrome, Safari, and Firefox, and found it to work in those contexts. I have not tested it extensively on mobile, but it seems that the autofocus behavior does not work on mobile Safari, Chrome may or may not work...
Thank you so much for your help on this! Very glad that my company chose Stripe - great API, great tech support.
Sorry @darrenklein , are u sure it works on mobile safari?
@danielCommitted No, I'm not sure that it does - @asolove-stripe commented about this above:
"Though we would caution you: we've seen auto-focus, especially when it eventually focuses inside an iframe, cause problems on Mobile Safari and Mobile Chrome. Things like the keyboard failing to open or the previously-focused item on the parent document failing to fire blur events. So be sure to test if you do this!"
I can confirm that this works on desktop Chrome, FireFox, and Safari, though.
@darrenklein Well, I hoped you tested it, and it's my QA mistake...
Do you or @asolove-stripe have any thought how to enable this feature also on Mobile Safari (It works on Mobile Chrome)? Thanks for the help.
@danielCommitted Sorry, when I posted the working component, I should've noted the contexts that it worked in - I'll edit the post to include that info. Interesting that you found that it worked for mobile Chrome - didn't work for me!
I don't have any thoughts about getting it to work on mobile Safari - this is part of a much larger project that I'm working on and it's on a tight deadline - haven't had a chance to investigate all of the little nuances that are going to need tweaking...
Most helpful comment
@asolove-stripe Perfect! You're right, this is a React feature - for reference, see the 'Caveats' section: https://reactjs.org/docs/refs-and-the-dom.html
My 'perfect' component is ready - here it is:
It should be noted that I've tested this component on desktop Chrome, Safari, and Firefox, and found it to work in those contexts. I have not tested it extensively on mobile, but it seems that the autofocus behavior does not work on mobile Safari, Chrome may or may not work...
Thank you so much for your help on this! Very glad that my company chose Stripe - great API, great tech support.