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
I click on input alike div and masked card number gets removed because of 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(),
to make <label>{maskedCardNumber}</label> inside container-div.
Changing conatiner-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.
Related
I'm currently working on a project where I want to show a custom Dialogue box with my Own Content ("Save your data into drafts before leaving"). I have tried different methods but can't find a proper way to do it. I explore all the previous questions on StackOverflow but they didn't work properly in my case.
useEffect(() => {
return () => {
window.onbeforeunload = function() {
return "do you want save your data into drafts before leave?";
};
}
},[])
Currently, I've written the above code in Plain JavaScript to do, but it's just showing the dialogue box on tab close and reload while not showing on custom click events to navigate to other pages or window back button.
React can't help me in this because they remove useBlocker, usePrompt from new releases. How can I achieve it?
One way of doing this is :
import { Prompt } from 'react-router'
const MyComponent = () => (
<>
<Prompt
when={shouldBlockNavigation}
message='Do you want ot save data before leave?'
/>
{/* Component JSX */}
</>
)
If wants on page refresh or browser closing then add:
useEffect(() => {
if (shouldBlockNavigation) {
window.onbeforeunload = () => true
} else {
window.onbeforeunload = undefined
}
},[]);
Second way is to use history if using react-router
useEffect(() => {
let unblock = history.block((tx) => {
// Navigation was blocked! Let's show a confirmation dialog
// so the user can decide if they actually want to navigate
// away and discard changes they've made in the current page.
let url = tx.location.pathname;
if (window.confirm(`Are you sure you want leave the page without saving?`)) {
// Unblock the navigation.
unblock();
// Retry the transition.
tx.retry();
}
})
},[]);
useEffect(() => {
const unloadCallback = (event) => {
event.preventDefault();
event.returnValue = "";
return "";
};
window.addEventListener("beforeunload", unloadCallback);
return () => {
window.addEventListener("popstate", confirmation());
window.removeEventListener("beforeunload", unloadCallback);
}
}, []);
I just did it with this code sample (actually I combine two events to show dialogue whenever users leave a page) and it's working fine for me. Thanks to all of you guys ... Especially #DrewReese for the help
First of all, I have to say this is my first question on stack ;)
I am trying to implement a reading via NFC on my test web, but i dunno why, the ndefReader doesn't works on startup, i have to press any field on the web to get it loaded (or asked for permission).
BUT, if i wrote some alerts to check why it doen't reach the function on startup, it works!!! (of course, it show alerts before). I don't know if when I accept the alert, I am interacting with the web and that's why it works, but anyways, I dunno why this happens (I need to click anywhere before starting).
function iniciar() {
document.getElementById("input1").focus();
//alert("test before"); <--- IF i remove this, it doesnt works
document.getElementById("input1").addEventListener("blur", async () => {
try{
const ndef = new NDEFReader();
alert("before wait");
await ndef.scan();
alert("after wait");
ndef.addEventListener("readingerror", () => {
alert("Argh! Cannot read data from the NFC tag. Try another one?");
});
ndef.addEventListener("reading", ({ message, serialNumber }) => {
alert(`> Serial Number: ${serialNumber}`);
alert(`> Records: (${message.records.length})`);
});
} catch (error) {
alert("Argh! " + error);
}
},false);
To scan and write to NFC tags, you must first request the "nfc" permission while handling a user gesture (e.g a button click, or in your case the "alert" call). Once handled, the NDEFReader scan() and write() methods trigger a user prompt, if access was not previously granted.
Check out https://web.dev/nfc/#security-and-permissions to learn more.
Hopefully https://googlechrome.github.io/samples/web-nfc/ samples should help you as well.
scanButton.addEventListener("click", async () => {
console.log("User clicked scan button");
try {
const ndef = new NDEFReader();
await ndef.scan();
console.log("> Scan started");
ndef.addEventListener("readingerror", () => {
console.log("Argh! Cannot read data from the NFC tag. Try another one?");
});
ndef.addEventListener("reading", ({ message, serialNumber }) => {
console.log(`> Serial Number: ${serialNumber}`);
console.log(`> Records: (${message.records.length})`);
});
} catch (error) {
console.log("Argh! " + error);
}
});
Cypress overwrite: I would like to overwrite the existing visit command so that it still operates as is, but will attempt to dismiss a popup after the visit has successfully executed.
The popup is something we have very little control over and it appears after you login. Seeing as we're bypassing the login screen and logging in programmatically, we'll see the popup when we navigate to any page. The insufficient code I currently have:
Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
originalFn(url, options);
cy.get("body").then($body => {
if ($body.find("[text='Got it']").length > 0) {
cy.contains("Got it", { matchCase: false }).click();
}
});
});
Thanks
You can do this by overwriting cy.visit() command. Try this:
Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
originalFn(url, options);
// make sure to add a return here!
return cy.get('body').then($body => {
if ($body.find("[text='Got it']").length > 0) {
cy.contains('Got it', { matchCase: false }).click();
}
});
});
source: https://docs.cypress.io/api/cypress-api/custom-commands#Overwrite-visit-command
I have a Wix site and I have this JS code in the site:
import { fetch } from 'wix-fetch';
$w("#button1").onClick( (event) => {
var link = $w("#link").id;
console.log(link)
fetch("https://my-new-app-xyz.herokuapp.com?link="+link, {"method": 'get'})
.then((httpResponse) => {
if (httpResponse.ok) {
return httpResponse;
} else {
return Promise.reject("Failed");
}
} )
.catch( (err) => {
console.log(err);
} );
})
However, upon the click of button1 nothing happens. Hopefully, the code explains what I want to do, but, upon clicking button1. I want to get the value of the text box element with id link and send a GET request to "https://my-new-app-xyz.herokuapp.com?link=" + the link from the text box. I don't know much JavaScript - this code is from reading the Wix API docs.
I would like to show the popup only one time with React Hooks.
Access for the first time to example.com/campaign/1234
Show popup
Close or refresh the page.
Access again to example.com/campaign/1234 and don't show popup
Access for the first time to example.com/campaign/0000 (is a different URL)
Show popup
Close or refresh the page
Access again to example.com/campaign/0000 or example.com/campaign/1234 and the popup is not being displayed
Any idea of how to do it? I know that I need to use local storage but how can I trigger the event when the user closes or refreshes the page?
Here is a sandbox.
I also read this thread but it doesn't mention how to do it with Hooks
If you never use the setStickyState callback from the custom hook, the state will just remain at its initial value.
It seems like setStickyState also has a bug in it, where it won't update if the key has changed. Here's an enhanced version that I've called useLocalStorage, which should work more reliably:
export function useLocalStorage(key, initialDefault) {
const [val, setVal] = useState(() => {
const localStorageVal = localStorage.getItem(key);
return localStorageVal !== null
? JSON.parse(localStorageVal)
: initialDefault;
});
useEffect(() => {
if (localStorage.getItem(key) === null) {
setVal(initialDefault);
}
}, [key, initialDefault]);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(val));
}, [val, key]);
return [val, setVal];
}
You can then use it like this:
const [visited, setVisited] = useLocalStorage(pageId, false);
const navigateAway = useCallback(() => {
setVisited(true)
}, [setVisited])
useEffect(() => {
// if user navigates away to a completely different site
// or refreshes the page etc
window.addEventListener("beforeunload", navigateAway);
// if user navigates to another page on the same site
return () => {
navigateAway();
window.removeEventListener("beforeunload", navigateAway);
};
}, [pageId, navigateAway]);
// ...
<dialog open={!visited}>
<p>Welcome to page {pageId}!</p>
<button onClick={() => setVisited(true)}>
Don't show again on this page
</button>
</dialog>
Here's a demo (with TypeScript):
useLocalStorage demo