Unable to create new AdyenCheckout in REACT - javascript

I am new to Adyen and relatively new to Javascript and REACT. I am trying to use the Adyen dropin components in REACT but cannot create a new AdyenCheckout component.
I have loaded the Adyen Javascript in componentDidMount using the following code:
const script = document.createElement("script");
script.src = "https://checkoutshopper-
test.adyen.com/checkoutshopper/sdk/3.0.0/adyen.js";
script.async = true;
document.body.appendChild(script);
And I am trying to create the AdyenCheckout component using the code below:
const configuration = {
locale: "en_US",
environment: "test",
originKey: "YOUR_ORIGIN_KEY",
paymentMethodsResponse: this.state.paymentMethodsResponse,
};
const checkout = new AdyenCheckout(configuration);
const dropin = checkout
.create('dropin', {
onSubmit: (state, dropin) => {
},
onAdditionalDetails: (state, dropin) => {
}
})
.mount('#dropin');`
Or, by changing
new AdyenCheckout(configuration)
to new window.AdyenCheckout(configuration),
as people seem to have had success with this syntax in the past.
Using new AdyenCheckout(configuration)
, I get the error AdyenCheckout is not defined.
Using new window.AdyenCheckout(configuration)
, I get the error TypeError: window.AdyenCheckout is not a constructor.
I am sure its something pretty simple I am doing wrong so if anyone can help it would be appreciated.
Please help!

What's going on here is that you're trying to initiate AdyenCheckout before the script being actually loaded.
The easiest solution for these king of cases is to add a script tag in the HTML document. That way the script will be loaded before the React App is initiated.
That being said, since you'll only use the script in a specific section, adding the script tag in your React app does make sense.
To solve the issue, just move all the functionality related to AdyenCheckout to a method you call once the script is loaded:
class AdyenDropin extends Component {
constructor(props) {
super(props);
this.initAdyenCheckout = this.initAdyenCheckout.bind(this);
}
componentDidMount() {
const script = document.createElement("script");
script.src =
"https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.0.0/adyen.js";
script.onload = this.initAdyenCheckout; // Wait until the script is loaded before initiating AdyenCheckout
document.body.appendChild(script);
}
initAdyenCheckout() {
// ...
Here you have a working example.
Cheers!

Use componentDidMount to initialize the AdyenCheckout object as it will have access to DOM.
class AdyenDropin extends Component {
contructor(props){
this.checkout = {};
}
componentDidMount(){
const script = document.createElement("script");
script.src = "https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.0.0/adyen.js";
script.async = true;
document.body.appendChild(script);
const configuration = {
locale: "en_US",
environment: "test",
originKey: "YOUR_ORIGIN_KEY",
paymentMethodsResponse: this.state.paymentMethodsResponse,
};
const checkout = new AdyenCheckout(configuration);
const dropin = checkout
.create('dropin', {
onSubmit: (state, dropin) => {
},
onAdditionalDetails: (state, dropin) => {
}
})
.mount('#dropin');`
}
}
render(){
return <div id="dropin"></div>
}
}

Related

Loading webpack bundle via script tag for microfrontend approach

I'm following a couple articles on how to implement a simple micro-frontend approach with React (here and here, see sample repos at bottom of question).
It works perfectly when the two apps (the root app, and the subapp) are running in their respective developoment servers. However, when I deploy the build artifacts to the real web server, it doesn't work. This is the important code in the root app:
function MicroFrontend({name, host, history}) {
useEffect(() => {
const scriptId = `micro-frontend-script-${name}`;
const renderMicroFrontend = () => {
window["render" + name](name + "-container", history);
};
if (document.getElementById(scriptId)) {
renderMicroFrontend();
return;
}
fetch(`${host}/asset-manifest.json`)
.then((res) => res.json())
.then((manifest) => {
const script = document.createElement("script");
script.id = scriptId;
script.crossOrigin = "";
script.src = `${host}${manifest.files["main.js"]}`;
script.onload = () => {
renderMicroFrontend();
};
document.head.appendChild(script);
});
return () => {
window[`unmount${name}`] && window[`unmount${name}`](`${name}-container`);
};
});
return <main id={`${name}-container`}/>;
}
MicroFrontend.defaultProps = {
document,
window,
};
When I click on the button that loads the main.js for the micro-app, it works fine up till the point where it calls the renderMicroFrontend above. Then I get this error in my browser: Uncaught TypeError: window[("render" + t)] is not a function
This is because it can't find the function that loads the microfrontend that is supposed to be on window. When I run the two apps in the dev server, I have the correct function on window and it works. When I follow the same steps with the two apps deployed to a real server (after running npm run build), instead of having the renderMicroApp function on my window, I have a different variable called: webpackJsonpmicro-app.
I figured out that this is due to the output.library option (and/or related options) in webpack, from the webpack docs:
output.jsonpFunction.
string = 'webpackJsonp'
Only used when target is set to 'web', which uses JSONP for loading on-demand chunks.
If using the output.library option, the library name is automatically concatenated with output.jsonpFunction's value.
I basically want the bundle/script to be loaded and evaluated, so that the renderMicroApp function is available on the window, but I'm lost regarding what webpack settings I need for this to work, since there are lots of different permutations of options.
For reference, I'm using the following config-overrides in the micro-app (atop react-app-rewired):
module.exports = {
webpack: (config, env) => {
config.optimization.runtimeChunk = false;
config.optimization.splitChunks = {
cacheGroups: {
default: false,
},
};
config.output.filename = "static/js/[name].js";
config.plugins[5].options.filename = "static/css/[name].css";
config.plugins[5].options.moduleFilename = () => "static/css/main.css";
return config;
},
};
And in my index.tsx (in the micro app) I'm exposing the function on the window:
window.renderMicroApp = (containerId, history) => {
ReactDOM.render(<AppRoot />, document.getElementById(containerId));
serviceWorker.unregister();
};
Some sample repos:
https://github.com/rehrumesh/react-microfrontend-container
https://github.com/rehrumesh/react-microfrontend-container-cats-app.git
https://github.com/rehrumesh/react-microfrontend-container-dogs-app
You could try with terser-webpack-plugin.
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
webpack: (config, env) => {
config.optimization.minimize = true;
config.optimization.minimizer = [new TerserPlugin({
terserOptions: { keep_fnames: true }
})];
config.optimization.runtimeChunk = false;
config.optimization.splitChunks = {
cacheGroups: {
default: false,
},
};
config.output.filename = "static/js/[name].js";
config.plugins[5].options.filename = "static/css/[name].css";
config.plugins[5].options.moduleFilename = () => "static/css/main.css";
return config;
}
};

How to reload localStorage VueJS after using this.$router.go(-1);

This is my Login.vue:
mounted() {
if (localStorage.login) this.$router.go(-1);
},
methods: {
axios.post(ApiUrl + "/login") {
...
}
then(response => {
...
localStorage.login = true;
this.$router.go(0); /* Reload local storage */
})
}
App.vue:
mounted() {
axios
.get("/user")
.then(response => {
localStorage.user_id = response.data.user.id;
localStorage.package_id = response.data.user.package_id;
})
},
Project.vue:
mounted() {
this.user_id = localStorage.user_id
this.package_id = localStorage.package_id
}
With that above code, I cannot get localStorage.user_id and localStorage.package_id as I expected. But if I change like the follow, it worked.
mounted() {
const self = this
setTimeout(function () {
self.user_id = localStorage.user_id
self.package_id = localStorage.package_id
self.getProject();
},1000)
}
But I think setTimeout not good in that case. Is there any way to refactor this code?
Thank you!
Try this: in your root component (it's usually const app = new Vue({ ... })) write the following:
import {localStorage} from 'localStorage'; // import your module if necessary
// this is relative to the way you manage your dependencies.
const app = new Vue({
//...
data: function() {
return {
localStorage: localStorage;
}
}
})
Now whenever you want to use localStorage, access it from root component like this:
this.$root.localStorage
Hope this solves your problem.
Don't know your project structure ,but I guess it's probably an async issue. You got the user information async, so when Project.vue mounted, the request is not complete yet. As a result, the localstorage is empty at the monent.
There are two solutions for this:
Make sure Project.vue is not rendered before userinfo is complete. For example, things like <project v-if="userinfo.user_id" /> should works.
Use some data binding libary like vuex to bind userinfo to Project.vue instead of assign it in lifecycle like mounted or created.
Hope it helps.

Dynamic Imports - NextJS

I have a simple function which loads a script:
const creditCardScript = (
onReadyCB,
onErrorCB,
) => {
let script = document.createElement("script");
script.type = "text/javascript";
script.src = process.CREDIT_CARD_SCRIPT;
document.head.appendChild(script);
script.onload = function() {
...
};
};
export default creditCardScript;
Before I migrated to NextJS, I was importing the script with: import creditCardScript from "./creditCardScript".
Sine NextJS renders components server side in Node, care needs to be taken to ensure that any code with a reference to window (which is browser specific), doesn't get called until componentDidMount.
NextJS solves this issue by providing dynamic imports (a wrapper around react-loadable) which:
only load the component when needed,
provides an option to only load the component on client side
(ssr: false).
I went ahead and implemented dynamic imports:
const creditCardScript = dynamic(import("./creditCardScript"), { ssr: false });
In componentDidMount:
componentDidMount = () => {
creditCardScript(
this.onReadyCB,
this.onErrorCB
);
};
But I'm getting this:
Uncaught TypeError: Cannot call a class as a function
I've tried to convert the function to a class and use the constructor to pass in args, but my code now fails silently.
As Neal mentioned in the comments, all I need to do is something like this in componentDidMount:
const { default: creditCardScript } = await import("./creditCardScript");
Link to the official tutorial
Export default only work with import from statement, you can try
export creditCardScript;
And on import, u can use like this
const {creditCardScript} = dynamic(import("./creditCardScript"), { ssr: false });

Angular2 + Angular1 + ES5 syntax component subscription

First of all let me say I am still using ES5, mostly because I am writing this for a frontend of a Google Apps Scripts application and didn't have the time/patience to make TypeScript work.
I am currently using this method in order to upgrade my Angular1 app to Angular2:
http://www.codelord.net/2016/01/07/adding-the-first-angular-2-service-to-your-angular-1-app/
I have a overlayLoaderService service to show a loading spinner in an overlay div with simple functions to get and set the loading state, and a overlayLoader component to show the div itself.
Service:
var overlayLoaderService = ng.core.
Injectable().
Class({
constructor: function() {
this.loading = false;
this.stateChange = new ng.core.EventEmitter();
},
setState: function(state) {
this.loading.value = state;
this.stateChange.emit(state);
},
getState: function() {
console.log(this.loading);
}
});
upgradeAdapter.addProvider(overlayLoaderService);
angular.module('Gojira').factory('overlayLoaderService', upgradeAdapter.downgradeNg2Provider(overlayLoaderService));
Component:
var OverlayLoaderComponent = ng.core
.Component({
selector: 'overlay-loader',
template: '<div [hidden]="loading" id="overlay-loader"></div>',
providers: [overlayLoaderService]
}).Class({
constructor: [overlayLoaderService, function(overlayLoaderService) {
this.loading = !overlayLoaderService.loading.value;
this._subscription = overlayLoaderService.stateChange.subscribe(function (value) {
console.log(value);
this.loading = !value;
});
}],
});
angular.module('Gojira').directive('overlayLoader', upgradeAdapter.downgradeNg2Component(OverlayLoaderComponent));
What I am trying to do is to achieve is the component to update its loading property when I call setState() method in the overlayLoaderService.
The subscription is never called, so I guess I am doing something terribly wrong here.
Any help would be appreciated.
remove
providers: [overlayLoaderService]
from OverlayLoaderComponent should work, as the provider has already been added to the adapter. Otherwise, it seems like angular 1 component and angular 2 component are using different instance of that service.

Initialising Okta Signin Widget a second time in a single-page webapp throws exception

We are integrating the Okta Sign-in Widget into our React-based webapp.
The example snippet:
var oktaSignIn = new OktaSignIn({baseUrl: baseUrl});
oktaSignIn.renderEl(...)
Works fine for us when rendering the widget for the first time, but after the user logs in and logs out again, the webapp renders the login component a second time and would attempt to execute the renderEl again to render the widget. This causes the following exception to be thrown:
Backbone.history has already been started
I have created this jsfiddle to demonstrate the problem. It just instantiates a signin widget twice (the second time after a wait). You can see that the second invocation causes the exception to be thrown.
https://jsfiddle.net/nudwcroo/6/
At the moment my workaround is to reload the entire webapp when going to the login component but that is undesirable for a single page app.
Is this a known issue? Is there any way to initialise the sign-in widget twice in a single javascript session?
Since the widget can only be instantiated once per page, it is best to hide/show the element for all Single Page Applications.
<div id="okta-login-container"></div>
<script type="text/javascript">
var oktaSignIn = new OktaSignIn(/* config */);
oktaSignIn.renderEl(
{ el: '#okta-login-container' },
function (res) {
if (res.status === 'SUCCESS') {
// Hide element
$("#okta-login-container").hide();
}
}
);
</script>
When you create your logout() function, make sure to show() the element instead of rendering it again.
function logout() {
$('#okta-login-container').show();
// Do more logic
}
For those experiencing similar problems after following the Okta example provided here: (https://github.com/okta/samples-js-react/blob/master/custom-login/src/Login.jsx)
The problem is with attempting to initialize the widget multiple times within a single page application. I fixed this by only initializing the widget once at the App level, and then rendering it and removing it from the DOM when a child component mounts/unmounts.
Example:
//App.js
class App extends Component {
constructor() {
super()
this.signIn = new OktaSignIn({...})
}
render() {
return <SignInPage widget={this.signIn} />
}
}
--
//SignInPage.js
...
componentDidMount() {
let { redirectUri } = this.state
let { widget } = this.props
widget.renderEl(
{ el: '#sign-in-widget' },
(response) => {
response.session.setCookieAndRedirect(redirectUri)
},
(error) => {
throw error;
},
);
}
componentWillUnmount() {
let { widget } = this.props
widget.remove()
}
render() {
return <div id="sign-in-widget"/></div>
}

Categories