Background:
Working on Cybersource Credit Card integration with React app. Need to show Masked card number coming from API response upon tabbing out (onBlur
) of field.
Flow
Created a container-div in which IFrame gets loaded and I enter CC number. On blur
event, doing API calls for validation and getting the masked card number (if successfully validated).
Upon setting maskedCardNumber
, component gets re-rendered and shows the maskedCardNumber in the container-div. At this point I see in Elements tab, IFrame is gone which is perfectly fine to me. Now in my container div there is no IFrame but a masked card number.
I want to change CC number
setMaskedCardNumber("");
. ( Not each character one by one but all in once as we do not have that card number). Till this point everything is fine.Problem
After removal, it should show new Iframe because I am calling loadIFrame();
like I did for initial Iframe loading but weirdly it does not show until I click AGAIN. YES!! you read it right. I need to click AGAIN to load and IFrame which is very weird for me.
Tried so far
useState()
,useReducer()
,<label>{maskedCardNumber}</label>
inside container-div.<div>
to <input>
Relevant code
useEffect(() => {
if (apiKey) {
loadIFrame();
}
}, [apiKey])
const loadIFrame = () => {
let flex;
flex = new Flex(apiKey);
let microform = flex.microform();
let number = microform.createField('number', {
placeholder: 'Enter card number'
});
number.on('load', () => {
number.focus();
});
number.on('blur', () => {
//transient token call
microform.createToken({}, function(err, token) {
if (err) {
console.error(err);
setCardError({
...error,
token: "Please enter valid CC number"
})
} else {
setCardError({
...error,
token: ""
});
//permenant token call
setMaskedCardNumber(JSON.parse(atob(token.split('.')[1]))["data"]["number"]);
dispatch({
type: orderActions.GET_CC_TOKEN,
transientToken: JSON.parse(atob(token.split('.')[1]))['jti'],
callbacks: {
success: (ccToken) => {
updateCardDetail({
type: "token",
token: ccToken
})
},
failure: (err) => {
console.error(err);
setCardError({
...error,
token: "Please enter valid CC number"
})
}
}
})
}
})
});
number.load('#number-container');
}
<div id="number-container" className="form-control" onClick={() => {
setMaskedCardNumber("");
loadIFrame();
}}>
{maskedCardNumber}
</div>
Problem I see here is basically the response to your setMaskedCardNumber()
call returns after the iFrame is loaded due to its async nature.
Try putting this effect
useEffect(()=>{
if(maskedCardNumber === ""){
loadIFrame()
}
}, [maskedCardNumber])
and remove loadIFrame();
from onClick()
handler
this will make sure whenever you empty the cardNumber state, iFrame is loaded when it is successfully emptied.
UPDATE:
We can make use of single useEffect
.
useEffect(() => {
if (apiKey && !maskedCardNumber) {
loadIFrame();
}
}, [apiKey, maskedCardNumber])
With this we can make sure to load IFrame only if apiKey present otherwise do not try to load because IFrame is dependant on apiKey.