React- only run file once external js file loaded (gapi not defined) - javascript

I am trying to use the gmail API with React.js.
I keep getting the error 'gapi is not defined'. I believe my client.js file in the HTML is loading after my mail.js file runs?
How can I get around this?
Index.html
...
<script src="https://apis.google.com/js/client.js"></script>
Index.js
import './Mail.js';
Mail.js
import { createAction, handleActions } from 'redux-actions'
const CLIENT_ID = '1.apps.googleusercontent.com'
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
export const SET_GMAIL_CREDENTIALS = 'SET_GMAIL_CREDENTIALS'
export const CHANGE_LOADING = 'CHANGE_LOADING'
export const SET_GMAIL_LABELS = 'SET_GMAIL_LABELS'
export const SELECT_GMAIL_LABEL = 'SELECT_GMAIL_LABEL'
export const SET_GMAIL_EMAILS = 'SET_GMAIL_EMAILS'
let defaultState = {
profile: {
emailAddress: ''
},
loading: true,
labels: [],
currentLabel: null,
emails: []
}
export const connect = () => {
return (dispatch, getState) => {
dispatch(turnLoadingOn())
gmailAuth(false, populateCredentials(dispatch), clearCredentials(dispatch))
}
}...

I think you're right. The way I'm handling these situations is by loading the external JS file from React and using it in a promise.
So your flow should be something like this:
React app loads
React app injects your file in the HTML
Do your thing in step 2's callback or .then()
Create a helper function. Put it in a folder like helpers/load-script. Below you have all the code you should have in that file:
export default function loadScript(url, cb) {
var scr = document.createElement('script');
scr.type = 'text/javascript';
if (scr.readyState) { // IE
scr.onreadystatechange = function() {
if (scr.readyState ==`loaded' || scr.readyState ==='complete') {
scr.onreadystatechange = null;
cb();
}
};
} else { // Others
scr.onload = cb;
}
script.src = url;
document.getElementsByTagName('head')[0].appendChild(scr);
}
Next, import that function inside the component you want to use it into:
import React from 'react';
import loadScript from 'helpers/load-script';
class testComponent extends React.Component {
componentDidMount() {
loadScript('https://apis.google.com/js/client.js', () => {
// do mail api stuff here
});
}
render() {
return (
<div>hi there</div>
);
}
}
export default testComponent;

I had the same problem with GrowSurf Javascript Web API, The external script loaded after the render function, and its function was being undefined in componentDidMount().
This is a logic I used to get the GrowSurf function not to be undefined. It can also help anyone who wants to use a function of any external JS introduced in index.html.
You can use DOMSubtreeModified in componentDidMount() to check every time the DOM is modified, once the external JS is loaded(found) run your function there, and then stop the looping of DOMSubtreeModified.
componentDidMount() {
let functionDefined = false;
window.addEventListener('DOMSubtreeModified', function () {
if(!functionDefined) {
if (window.growsurf) {
console.log('a function is defined', window.growsurf.getReferrerId());
functionDefined = true;
}
}
}, false);
}
For your case, you can simply do this.
componentDidMount() {
let functionDefined = false;
window.addEventListener('DOMSubtreeModified', function () {
if(!functionDefined) {
if (window.gapi) {
// do mail api stuff here
functionDefined = true;
}
}
}, false);
}

Related

Navigo Javascript routing: Every route I register didn't match any of the registered routes

I'm learning how to make a single page app with javascript.
My javascript teacher provided a beautiful tutorial how to create a single page application from scratch. I followed the tutorial and everything went well untill the part where the routing came in..
He uses a library which is called navigo. I don't know why but it seems to not working for me at all.
The moment I've written the final line of code. My homepage disappeared and the console gave a warning that my route '/' which is my homepage, didn't match any of the registered routes, but it looks like there is no route registered at all, while I'm definitly registering them..
here is my code
My root index.js
import './sass/main.scss';
import App from './App';
import { HomeComponent, NewEventComponent } from './Components';
// Retrieve appComponent
const initApp = () => {
const appContainer = document.getElementById('appContainer');
const app = new App(appContainer);
app.addComponent(new HomeComponent());
app.addComponent(new NewEventComponent());
};
window.addEventListener('load', initApp);
My App.js (here is where my route is defined for every component. routerPath makes it dynamic )
// The App Wrapper
import Component from './lib/Component';
import Router from './Router';
class App {
constructor(parent) {
this.parent = parent;
this.components = [];
}
clearparent() {
while (this.parent.firstChild) {
this.parent.removeChild(this.parent.lastChild);
}
}
addComponent(component) {
if (!(component instanceof Component)) return;
// get the name from our component
const { name, routerPath } = component;
// when a component asks to reRender
component.reRender = () => this.showComponent(component);
// add to internal class
this.components.push(component);
// add to router
Router.getRouter().on(routerPath, () => {
this.showComponent({ name });
}).resolve();
}
showComponent({ name }) {
const foundComponent = this.components.find((component) => component.name === name);
if (!foundComponent) return;
this.clearparent();
this.parent.appendChild(foundComponent.render());
}
}
export default App;
The Home Component
// The Home Component
import Component from '../lib/Component';
import Elements from '../lib/Elements';
class HomeComponent extends Component {
constructor() {
super({
name: 'home',
model: {
counter: 0,
},
routerPath: '/',
});
}
incrementCounter() {
this.model.counter += 1;
}
render() {
const { counter } = this.model;
// create home container
const homeContainer = document.createElement('div');
// append header
homeContainer.appendChild(
Elements.createHeader({
textContent: `Current value is: ${counter}`,
}),
);
// append button
homeContainer.appendChild(
Elements.createButton({
textContent: 'increase',
onClick: () => { this.incrementCounter(); },
}),
);
return homeContainer;
}
}
export default HomeComponent;
A Component
// My components
class Component {
constructor({
name,
model,
routerPath,
}) {
this.name = name;
this.model = this.proxyModel(model);
this.routerPath = routerPath;
this.reRender = null;
}
proxyModel(model) {
return new Proxy(model, {
set: (obj, prop, value) => {
obj[prop] = value;
if (this.reRender) this.reRender();
return true;
},
});
}
}
export default Component;
The Router
// My Router
import Navigo from 'navigo';
const Router = {
router: null,
getRouter() {
if (!this.router) {
const rootUrl = `${window.location.protocol}//${window.location.host}`;
this.router = new Navigo(rootUrl, false);
}
return this.router;
},
};
export default Router;
Solution: I switched to Navigo(^7.0.0) and it works!
I seem to have the same problem as you. I'm also using navigo (^8.11.1). The problem is fixed for me when I declare a new router like this: new Navigo('/', false).
It still gives me the warning now, but it loads the page. sadly, this will only work in a dev environment

Adding a script with a React hook

I'm trying to add inline scripting to a React component:
I have my custom hook:
import { useEffect } from 'react';
const useScript = url => {
useEffect(() => {
const script = document.createElement('script');
script.src = url;
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
}
}, [url]);
};
export default useScript;
Which I use like so:
import useScript from 'hooks/useScript';
const MyComponent = () => {
useScript('path/to/my.js');
}
Script added to the document after SPA navigation event but it doesn't add events to my buttons without reloading the page.
I had a problem inside one of the added scripts. I changed the way of adding script. I exported the function inside js, then I imported this function inside my component and then I used useEffect.
import { MyFunction } from './myscript.js';
const MyComponent = () => {
useEffect(() => {
MyFunction();
}, []);
return(
......
)

How to call a function to change the state within a callback function?

I am implementing a Welcome Display web app that takes a guest name received from RabbitMQ and populates it on the screen. In the callback function of the stompClient.subscribe(... I want to call the function to change the state of the reservation and view on the screen. When I call the function it says the function is not defined. How can I change the state every time I receive the message?
import React from 'react';
import '../css/App.css'
import WelcomeVisitor from '../pages/WelcomeVisitor';
import ThankYou from '../pages/ThankYou';
import Stomp from 'stompjs'
class App extends React.Component {
constructor(props){
super(props)
this.state = {
currentView: 'ThankYou',
currentReservation: null
}
this.siteId = props.match.params.siteId
this.bayNumber = props.match.params.bayNumber
this.changeView = this.changeView.bind(this)
this.connectRabbit = this.connectRabbit.bind(this)
}
changeView(view){
this.setState({
currentView: view
})
}
changeReservation(reservation){
this.setState({
currentReservation: reservation
})
}
render(){
let view = ''
this.connectRabbit(this.siteId, this.bayNumber)
if(this.state.currentView === 'ThankYou'){
view = <ThankYou changeView={this.changeView}/>
} else if(this.state.currentView === 'WelcomeVisitor') {
view = <WelcomeVisitor guestName='Quinton Thompson'/>
}
return (
<div className="App">
{view}
</div>
)
}
connectRabbit(siteId, bayNumber){
let stompClient
var ws = new WebSocket('ws://localhost:15674/ws')
const connectHeaders = {
'login': 'guest',
'passcode': 'guest',
}
const queueHeaders = {
'x-queue-name': `${bayNumber}.visit.out.display`,
'durable': 'true',
'auto-delete': 'false'
}
stompClient = Stomp.over(ws)
stompClient.connect(connectHeaders , function(frame){
console.log('Connected')
stompClient.subscribe('/exchange/ds.game/visit.out',function(message){
//changeReservation and changeView is not defined
this.changeReservation(message.body)
this.changeView('WelcomeVisitor')
}, queueHeaders)
console.log('here')
})
}
}
export default App;
The this object in your function callback is likely not referencing the this object in your class.
Changing the function syntax to: (message) => {} and (frame) => {} should make it work. See below:
stompClient.connect(connectHeaders ,(frame) => {
console.log('Connected')
stompClient.subscribe('/exchange/ds.game/visit.out', (message) => {
//changeReservation and changeView is not defined
this.changeReservation(message.body)
this.changeView('WelcomeVisitor')
}, queueHeaders)
console.log('here')
})
While the code snippet above would make your code work,
ideally we should avoid writing these types of callback initializations on the fly ( in render method ), maybe better way of doing it would be creating function calls and referencing those as callbacks. Something like this ( more improvements can be made but just as an example ) :
connectCallback(stompClient, queueHeaders, frame) {
console.log('Connected');
stompClient.subscribe('/exchange/ds.game/visit.out', (message) => {
this.subscribeCallback(message)
}, queueHeaders);
}
subscribeCallback(message) {
this.changeReservation(message.body)
this.changeView('WelcomeVisitor')
}
Then just use the two functions above as a callback in your render code.
Lastly, you might need to bind changeReservation(reservation) also before anything else.

How to use deeplearn.js in a React component

I am currently working on creating a project with react and deeplearn.js, and have reached a roadblock when combining the two. In my react application I am importing this deeplearnjs library model which I am using to do classification. Unfortunately, when I try to call the predict() method I get the following error:
TypeError: _this.variables is undefined
For the following part of code:
SqueezeNet.prototype.predictWithActivation = function (input, activationName) {
var _this = this;
var _a = this.math.scope(function () {
var activation;
var preprocessedInput = _this.math.subtract(input.asType('float32'), _this.preprocessOffset);
When I use the generated Javascript in a normal HTML it works perfectly, so I am unsure why I am getting this error within react. I have a feeling it has to do with stricter React rules or Javascript versioning, but I am not sure.
Thanks!
UPDATE
The simplest way to reproduce this is the following:
Create a new React app with create-react-app
Run yarn add deeplearn and yarn add deeplearn-squeezenet
Modify App.js to the following:
import React, { Component } from 'react';
import {ENV, Array3D} from 'deeplearn';
import {SqueezeNet} from 'deeplearn-squeezenet';
class App extends Component {
constructor() {
super();
var net = new SqueezeNet(ENV.math);
net.load();
var img = new Image(227, 227);
img.src = 'boat.jpg';
img.onload = function () {
var pixels = Array3D.fromPixels(img)
var res = net.predict(pixels);
};
}
render() {
return (
<div></div>
);
}
}
export default App;
Download the following file into the public folder: https://raw.githubusercontent.com/PAIR-code/deeplearnjs/master/models/squeezenet/cat.jpg
Run yarn start
For reference I am using react 16.2.0
Your code is presumably failing because some of the method calls are asynchronous (.load() for example).
Here is how you would make your example work with React:
Create a new React app with create-react-app
Run yarn add deeplearn and yarn add deeplearn-squeezenet
Add cat.jpg to the public folder
Modify App.js as below
import React, { Component } from 'react';
import { ENV, Array3D } from 'deeplearn';
import { SqueezeNet } from 'deeplearn-squeezenet';
const math = ENV.math;
const squeezeNet = new SqueezeNet(math);
class App extends Component {
constructor() {
super();
this.state = {
statusText: 'Loading Squeezenet...'
}
}
buildSuggestions(obj){
return Object.keys(obj).map(
key => `${obj[key].toFixed(5)}: ${key}`
);
}
imageLoadHandler(e) {
const img = e.target;
squeezeNet.load()
.then(() => {
this.setState({ statusText: 'Predicting...' });
const pixels = Array3D.fromPixels(img);
const result = squeezeNet.predict(pixels);
this.setState({ statusText: '' });
squeezeNet.getTopKClasses(result, 5)
.then((obj) => {
this.setState({ statusText: this.buildSuggestions(obj) });
});
});
}
render() {
const text = Array.isArray(this.state.statusText)?
this.state.statusText :
[this.state.statusText];
return (
<div>
<img src="cat.jpg"
alt="cat"
onLoad={this.imageLoadHandler.bind(this)}
/>
<div id="result">
{ text.map(el => <div key={el}>{el}</div>) }
</div>
</div>
);
}
}
export default App;
Then run yarn start

Conditional import or alternative in JavaScript (ReactJS WebApp)?

I'm implementing internationalization for a ReactJS webapp.
How can I avoid loading all language files?
import ru from './ru';
import en from './en';
// next lines are not important for this question from here
import locale from 'locale';
const supported = new locale.Locales(["en", "ru"])
let language = 'ru';
const acceptableLanguages = {
ru: ru,
en: en,
}
if (typeof window !== 'undefined') {
const browserLanguage = window.navigator.userLanguage || window.navigator.language;
const locales = new locale.Locales(browserLanguage)
language = locales.best(supported).code
}
// till here
// and here i'm returning a static object, containing all language variables
const chooseLang = () => {
return acceptableLanguages[language];
}
const lang = chooseLang();
export default lang;
Unfortunately there is no way to dynamically load modules in ES6.
There is an upcoming HTML Loader Spec which will allow for this functionality, so you could use a polyfill in order to use that.
const chooseLang = () => System.import(`./${language}`);
export default chooseLang;
However, this would now be promise-based so it would need to be called like so:
import language from "./language";
language.chooseLang().then(l => {
console.log(l);
});
But bear in mind, that spec could change radically (or be dropped altogether).
Another alternative would be to not store your localizations as Javascript modules, but as JSON instead, e.g.
en.json
{ "hello_string": "Hi!" }
language.js
const chooseLang = () => {
return fetch(`./${language}.json`)
.then(response => response.json());
};
Again, this would be promise based so would need to be accessed as such:
import language from "./language";
language.chooseLang().then(l => {
console.log(l.hello_string);
});
That solution would be fully ES6-compliant and would not rely on possible future features.
Looks like I am late here, but I would like to answer the approach I am using. I have an async component:
import React from 'react';
export default (loader, collection) => (
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.Component = null;
this.state = { Component: AsyncComponent.Component };
}
componentWillMount() {
if (!this.state.Component) {
loader().then((Component) => {
AsyncComponent.Component = Component;
this.setState({ Component });
});
}
}
render() {
if (this.state.Component) {
return (
<this.state.Component { ...this.props } { ...collection } />
)
}
return null;
}
}
);
And we call it using:
const page1 = asyncComponent(() => import('./page1')
.then(module => module.default), { name: 'page1' });
and then we use it with:
<Route path='/page1' component= {page1}/>
This will ensure that loading is done dynamically.
Take a look at the react-loadable package - if you can somehow encapsulate your language files into components, this might solve your problem.

Categories