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 });
Related
I am trying to send some text on basic of hosted url (where my build is deployed).but i am getting this error
ReferenceError: location is not defined
here is my code
https://codesandbox.io/s/laughing-mendel-pf54l?file=/pages/index.js
export const getStaticProps = async ({ preview = false, previewData = {} }) => {
return {
revalidate: 200,
props: {
//req.host
name: location.hostname == "www.google.com" ? "Hello" : "ccccc"
}
};
};
Can you show your imports, because it could be that you are importing router from 'next/client'
Assuming that you are using functional-based component
You need to import router as follows:
import {useRouter} from "next/router";
in your function body:
const router = useRouter();
getStaticProps() is executed at build time in Node.js, which has no location global object – Location is part of the browser API. Additionally, because the code is executed at build time, the URL is not yet known.
Change getStaticProps to getServerSideProps (see documentation). This will mean the function is called at runtime, separately for each request.
From the context object passed to getServerSideProps, pull out the Node.js http.IncomingMessage object.
On this object, look for the Host header.
export const getServerSideProps = async ({ req }) => {
return {
props: {
name: req.headers.host === "www.google.com" ? "Hello" : "ccccc"
}
};
};
Note:
I also changed == to ===, as it's generally advised to use the latter. The former can produce some unexpected results because of silent type conversions.
I also removed revalidate, as this is not applicable to getServerSideProps().
I'm trying to make a web worker to prevent stalling the React main thread. The worker is supposed to read an image and do various things.
The app was created using create-react-app.
Currently I have
WebWorker.js
export default class WebWorker {
constructor(worker) {
const code = worker.toString();
const blob = new Blob(['('+code+')()'], {type: "text/javascript"});
return new Worker(URL.createObjectURL(blob), {type: 'module'});
}
}
readimage.worker.js
import Jimp from "jimp";
export default () => {
self.addEventListener('message', e => { // eslint-disable-line no-restricted-globals
if (!e) return;
console.log('Worker reading pixels for url', e.data);
let data = {};
Jimp.read(e.data).then(image => {
// jimp does stuff
console.log('Worker Finished processing image');
})
postMessage(data);
})
};
And then in my React component AppContent.js I have
import WebWorker from "./workers/WebWorker";
import readImageWorker from './workers/readimage.worker.js';
export default function AppContent() {
const readWorker = new ReadImageWorker(readImageWorker);
readWorker.addEventListener('message', event => {
console.log('returned data', event.data);
setState(data);
});
// callback that is executed onClick from a button component
const readImageContents = (url) => {
readWorker.postMessage(url);
console.log('finished reading pixels');
};
}
But when I run it, I get the error
Uncaught ReferenceError: jimp__WEBPACK_IMPORTED_MODULE_0___default is not defined
How can I properly import a module into a web worker?
EDIT:
As per suggestions from Kaiido, I have tried installing worker-loader, and edited my webpack.config.js to the following:
module.exports = {
module: {
rules: [
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' }
}
]
}
};
But when I run it, I still get the error
Uncaught ReferenceError: jimp__WEBPACK_IMPORTED_MODULE_0__ is not defined
I'm not too much into React, so I can't tell if the module-Worker is the best way to go (maybe worker-loader would be a better solution), but regarding the last error you got, it's because you didn't set the type of your Blob when you built it.
In this case, it does matter, because it will determine the Content-Type the browser sets when serving it to the APIs that fetch it.
Here Firefox is a bit more lenient and somehow allows it, but Chrome is picky and requires you set this type option to one of the many javascript MIME-types.
const script_content = `postMessage('running');`;
// this one will fail in Chrome
const blob1 = new Blob([script_content]); // no type option
const worker1 = new Worker(URL.createObjectURL(blob1), { type: 'module'});
worker1.onerror = (evt) => console.log( 'worker-1 failed' );
worker1.onmessage = (evt) => console.log( 'worker-1', evt.data );
// this one works in Chrome
const blob2 = new Blob([script_content], { type: "text/javascript" });
const worker2 = new Worker(URL.createObjectURL(blob2), { type: 'module'});
worker2.onerror = (evt) => console.log( 'worker-2 failed' );
worker2.onmessage = (evt) => console.log( 'worker-2', evt.data );
But now that this error is fixed, you'll face an other error, because the format import lib from "libraryname" is still not supported in browsers, so you'd have to change "libraryname" to the path to your actual script file, keeping in mind that it will be relative to your Worker's base URI, i.e probably your main-page's origin.
I experienced the same problem. Firefox could not show me where exactly the error was (in fact it was plain misleading...) but Chrome did.
I fixed my problem by not relying on an import statement (importing one of my other files) which would only have worked within a React context. When you load a Worker script (via the blob()
/ URL() hack), it has no React context (as it is loaded at runtime and not at transpile time). So all the React paraphernalia __WEBPACK__blah_blah is not going to exist / be visible.
So... within react... import statements in worker files will not work.
I haven't thought of a workaround yet.
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.
If I were writing an application in pure JS, I would make a plugin connection like this:
App.js
var App = function(){ /* ... */ };
//...
App.prototype.regPlugin= function ( atr1, atr2, ... ) { /* ... */ };
//...
App.prototype.sendToEventBus = function ( atr1, ... ) { /* ... */ };
//...
var app = new App();
//...
var appModules = {};
//...
document.onreadystatechange = function () {
if ( document.readyState === 'complete' ){
for ( var module in AppModules ) {
if ( AppModules[ module ] ) {
try {
AppModules[ module ].init( app );
} catch(er) {
//...
}
}
}
}
//...
plugin.js
var MyPlugin = function (){ /*...*/ };
//...
MyPlugin.prototype.init = function ( app ) {
this.app = app;
//...
app.regPlugin( plugAtr0 );
//...
};
//...
MyPlugin.prototype.handleAny = function(){
this.app.sendToEventBus( /* my event */ );
};
//...
appModules.myPlugin = new MyPlugin();
How to similarly make a plugin to an application on svelte.js?
Custom Element is not very suitable for this.
Well, you can do something very similar if you're so inclined. Svelte only provides you with an UI component you can render anywhere you want on your page. It doesn't take over your whole JS.
One thing is that your Svelte application will most probably be bundled (Rollup or Webpack) using ES import statement. That means your code will live in ES modules, and local variables are not automatically attached to the window object in ES modules. So you've got to make that explicit. So your code would become something like this:
App.js (presumably your application entry point)
import App from './App.svelte'
const app = new App({
target: document.body,
props: {
name: 'world',
},
})
const appModules = {}
// expose appModules as a global variable
window.appModules = appModules
document.onreadystatechange = function() {
if (document.readyState === 'complete') {
debugger
for (var module in appModules) {
if (appModules[module]) {
try {
appModules[module].init(app)
} catch (er) {
//...
}
}
}
}
}
So now, app is your root Svelte component. It would lives in an App.svelte file. Svelte lets you add instance methods to components by exporting const or function.
App.svelte
You can export const or function to have instance methods on a Svelte component.
<script>
export function regPlugin(...) { ... }
// or
export const sentToEventBus(...) { ... }
</script>
...
And... Voilà? Is there anything more in your code?
One issue, maybe, with the above code is that the App component will be rendered before your plugins had a chance to register.
You can fix this with a prop in your App component. In order to be able to change the value of this prop from your "controller code", you can use the $set method of the component. You can also set the accessors option on your component. You can do this globally with a bundler plugin option, or you can enable it on individual components with <svelte:options>.
If you need to have some custom logic that run only once app is ready, you can do so in a "reactive statement".
App.svelte
<svelte:options accessors={true} />
<script>
export function regPlugin() {}
export function sentToEventBus() {}
export let ready = false
$: if (ready) {
// code to run when ready
}
</script>
{#if ready}
<!-- content to show when ready (all plugins initialized) -->
<!-- most likely, you'd put other Svelte components in there -->
{:else}
<div>Loading...</div>
{/if}
You can then toggle this prop when the app is ready to start:
App.js
document.onreadystatechange = function() {
if (document.readyState === 'complete') {
for (var module in appModules) {
...
}
app.$set({ ready: true })
// or
app.ready = true
}
}
Alternatively, you may prefer to move the plugin init code in your App component. Since you have a "static" piece of state here, in the appModules variable, you'd have to put it into the static <script context="module"> part of your component:
App.svelte
<script context="module">
// this block only runs once, when the module is loaded (same as
// if it was code in the root of a .js file)
// this variable will be visible in all App instances
const appModules = {}
// make the appModules variable visible to the plugins
window.appModules = appModules
// you can also have static function here
export function registerPlugin(name, plugin) {
appModules[name] = plugin
}
</script>
<script>
// in contrast, this block will be run for each new instance of App
...
let ready
document.onreadystatechange = function() {
if (document.readyState === 'complete') {
// NOTE appModules bellow is the same as the one above
for (var module in appModules) {
// ...
}
ready = true
}
}
</script>
{#if ready}
...
{/if}
The static function addPlugin would be accessible as a named export from other modules:
import { addPlugin } from './App.svelte'
This would probably be more suited in the context of a bundled app / app with modules, than attaching things to window (hence running into risks of conflict in the global namespace). Depends on what you're doing...
That type of plugin setup would still work, look into the Client-side component API
With component.$set you can change props from your plugin to the svelte component.
When you adding listeners to you plugin/app from inside svelte you might need additional assignments data = myPlugin.data for svelte to be able to react to changes.
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>
}
}