How to add google api script while rendering the component? - reactjs

I am trying to add the google api script programmatically when it is required. However, I get an error that google is not defined. I can see that the script is added in before the end of the body tag.
Earlier I had the script loaded in the index.html file however, I have created a different component elsewhere in the app now which require its own script as it has a different api key. Therefore, I had to remove the script from the index.html as it was giving an exception for multiple use of the script. Now I would like to add it when it is the component is loading.
Please refer the code below for the main component:
import React from 'react';
import { Button } from 'reactstrap';
import CitySuggestionBar from './CitySuggestionBar';
export default class Destination extends React.Component{
componentDidMount(){
this.renderScript();
}
renderScript = () => {
loadScript('https://maps.googleapis.com/maps/api/js?key=MY_API_KEY&libraries=places');
}
showPlaceDetails(place) {
let city = place.address_components[0].long_name.toString();
try{
city+= '+' + place.address_components[2].long_name.toString();
}catch(e){}
city = city.replace(/\s/g, "+");
sessionStorage.setItem('city', city);
console.log(city);
}
redirect = () =>{
sessionStorage.getItem('city') ? this.props.history.push("/hotels") : alert('Please select a city first');
}
render(){
return(
<div className="location-search-container">
<div className="location-search-wrapper">
<h1>Search for a city...</h1>
<CitySuggestionBar onPlaceChanged={this.showPlaceDetails.bind(this)} />
<Button onClick={this.redirect} className="btns" to="/hotels" color="primary">Proceed</Button>
</div>
</div>
);
}
}
const loadScript = (url) => {
const index = window.document.getElementsByTagName('script')[0];
const script = window.document.createElement('script');
script.src=url;
index.parentNode.insertBefore(script, index);
}
Below is the code for the component where the google map is being used and it is a sub component of the above main component:
import React from "react";
/* global google */
export default class CitySuggestionBar extends React.Component {
constructor(props) {
super(props);
this.autocompleteInput = React.createRef();
this.autocomplete = null;
this.handlePlaceChanged = this.handlePlaceChanged.bind(this);
}
componentDidMount() {
this.autocomplete = new window.google.maps.places.Autocomplete(this.autocompleteInput.current,
{"types": ['(cities)']});
this.autocomplete.addListener('place_changed', this.handlePlaceChanged);
}
handlePlaceChanged(){
const place = this.autocomplete.getPlace();
this.props.onPlaceChanged(place);
}
render() {
return (
<input ref={this.autocompleteInput} id="autocomplete" placeholder="Search"
type="text"></input>
);
}
}
Please Help!
Thanks in advance.

In the above snippet, I can see that every time the componentDidMount it will again create another script tag to avoid this, you can modify the loadScript methods as follows:
const loadScript = (url) => {
const googleScript = window.document.getElementByClassName('google-script');
if (googleScript.length === 0) {
const script = window.document.createElement('script');
script.src=url;
script.class="google-script"
document.body.appendChild(script)
}
}
If you like to remove the google script you can handle this inside componentWillUnmount.
Using this will not show you an exception for multiple uses of the script tag.
Also if you like to know that the script tag is loaded or not you can find it by adding another like in loadScript method as follows:
const loadScript = (url) => {
const googleScript = window.document.getElementByClassName('google-script');
if (googleScript.length === 0) {
const script = window.document.createElement('script');
script.src=url;
script.class="google-script"
document.body.appendChild(script)
script.onload = () => {
// Place code here to do further action.
};
}
}
<----------------------------Update--------------------------->
In order to resolve "google is undefined" error you can try following the approach where you create a promise for the Google Maps API, and resolve that promise in a (global) callback function the Google Maps API can run. In your component code, you'd then wait for the promise to be resolved before proceeding.
const loadScript = () => {
if (!this.googleMapsPromise) {
this.googleMapsPromise = new Promise((resolve) => {
// Add a global handler for when the API finishes loading
window.resolveGoogleMapsPromise = () => {
// Resolve the promise
resolve(google);
// Tidy up
delete window.resolveGoogleMapsPromise;
};
// Load the Google Maps API
const script = document.createElement("script");
const API = //your api key;
script.src = `https://maps.googleapis.com/maps/api/js?key=${API}&callback=resolveGoogleMapsPromise`;
script.async = true;
document.body.appendChild(script);
});
}
// Return a promise for the Google Maps API
return this.googleMapsPromise;
}
componentWillMount() {
// Start Google Maps API loading since we know we'll soon need it
this.loadScript();
}
componentDidMount() {
// Once the Google Maps API has finished loading, initialize the map
this.getGoogleMaps().then((google) => {
const uluru = { lat: -25.366, lng: 131.044 };
const map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: uluru
});
const marker = new google.maps.Marker({
position: uluru,
map: map
});
});
}
render() {
return (
<div>
<div id="map" style={{width: 600, height: 300}}></div>
</div>
)
}

Related

Edit iframe DOM from window

When i try to manipulate iframe i always get the cors issue permission denied to access property document on cross-origin, same goes when i try to attach event listener.
So is it possible to manipulate DOM elements inside iframe that is coming from different origin?
I'm happy to somehow also take a copy of iframe and just have it in my code as long as it allows me to edit it later, but unsure how?
import { useEffect } from 'react';
const post = () => {
const iframe = document.querySelector('iframe');
const message = { type: 'modifyDOM', text: 'Hello World!' };
// window.postMessage(message);
if (iframe?.contentWindow) {
iframe.contentWindow.postMessage(message, 'http://example.com');
}
};
export const IFrame = () => {
useEffect(() => {
const iframe = document.querySelector('iframe');
const receiveMessage = (e: MessageEvent) => {
if (e.data.type === 'modifyDOM') {
console.log(e.data);
// window.document.body.innerHTML = '<h1>Hello World!</h1>';
if (iframe?.contentWindow) {
iframe.contentWindow.document.body.innerHTML = '<h1>Hello World!</h1>';
}
}
};
window.addEventListener('message', receiveMessage);
return () => {
window.removeEventListener('message', receiveMessage);
};
}, []);
return (
<div>
<button onClick={post}>Post</button>
<iframe src="http://example.com" />
</div>
);
};

How to use PDF.JS with React?

I would like to parse a pdf file in a React app. The pdf will be provided through a html input.
I used pdf-parse - a wrapper around pdf.js in node - without any problem. But when it comes to React, I only receive this error:
MissingPDFException {message: 'Missing PDF "http://localhost:3000/myfile.pdf".', name: 'MissingPDFException'}
I upload the file like this:
export default function Home() {
const [data, setData] = useState();
const handleFile = (e) => {
const file = e.target.files[0];
const fileReader = new FileReader();
fileReader.onload = (d) => {
setData(new Uint32Array(d.target.result));
};
};
return (
<>
<h1>hello!</h1>
<input
type="file"
accept="application/pdf"
placeholder="insert PDF here"
onChange={(e) => handleFile(e)}
/>
<PDFViewer pdfFile={data} />
</>
);
}
And The file is supposed to be read here:
import * as PDFJS from "pdfjs-dist/build/pdf";
import * as pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
window.PDFJS = PDFJS;
export default function PDFViewer({ pdfFile }) {
PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const getPDFDoc = useCallback(async () => {
const doc = await PDFJS.getDocument(pdfFile);
doc.promise.then(
(loadedPdf) => {
setPdfRef(loadedPdf);
},
function (reason) {
console.error(reason);
}
);
}, []);
useEffect(() => {
getPDFDoc();
}, [getPDFDoc]);
I doesn't seem to work at all. I have a custom config with webpack, typescript and SWC-loader. I have read all the related stackoverflow threads.
How to properly parse a PDF with PDF.js in React? If there is a better library, I'm open to any suggestions. My goal is not to display the pdf, but to get its content.
Your component only runs getPDFDoc on mount since pdfFile is missing in the usecallback deps, so when the file changes, it probably doesn't even notice as your effect won't re-run since getPDFDoc is referentially stable when it shouldn't be.
Try
import * as PDFJS from "pdfjs-dist/build/pdf";
import * as pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
window.PDFJS = PDFJS;
export default function PDFViewer({ pdfFile }) {
PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const getPDFDoc = useCallback(async () => {
if (!pdfFile) return
const doc = await PDFJS.getDocument(pdfFile);
doc.promise.then(
(loadedPdf) => {
setPdfRef(loadedPdf);
},
function (reason) {
console.error(reason);
}
);
}, [pdfFile]);
useEffect(() => {
getPDFDoc();
}, [getPDFDoc]);
I think the reason for the weird "myfile.pdf" thing is probably because when it first runs pdfFile is not defined and this might be some internal library default. So I also added a guard to not do anything when it's not set.

Videojs custom TextTrackDisplay not working

I have requirement in my project to customize player controls. I found a lot documentation how to make it possible, except TextTrackDisplay component. For some reason this component has been rendered by videojs, but rendered code seems have no clue about any existing text tracks of standard hls stream (bip-bop).
I've created sample of code where play button successfully customized, but TextTrackDisplay wasn't for some reason...
https://codepen.io/jurij-sergeewich-gerc/pen/xxxwPmN
class Player extends React.Component {
init = (videoRef) => {
const videoJsOptions = {
controls: false,
html5: {nativeTextTracks: false},
};
const player = videojs(videoRef, videoJsOptions, () => {
const type = 'application/x-mpegURL';
const src = 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8'
player.pause();
player.src({type, src});
player.play();
this.connectPlayButton(player);
this.connectTextTracks(player);
});
}
connectPlayButton = (player) => {
const selector = '[data-play-btn]';
const allElements = Array.from(document.querySelectorAll(selector));
allElements.forEach((box) => {
const PlayToggle = videojs.getComponent('PlayToggle');
const playToggle = new PlayToggle(player);
box.appendChild(playToggle.el());
});
};
connectTextTracks = (player) => {
const selector = '[data-text-tracks]';
const allElements = Array.from(document.querySelectorAll(selector));
allElements.forEach((box) => {
const TextTrackDisplay = videojs.getComponent('TextTrackDisplay');
const textTrackDisplay = new TextTrackDisplay(player);
box.appendChild(textTrackDisplay.el());
});
};
render () {
return (
<div data-vjs-player>
<video ref={this.init} className="video-js-video">
</video>
<div data-play-btn></div>
<div data-text-tracks></div>
</div>
)
}
}
ReactDOM.render(
<Player/>,
document.getElementById('root')
);
Could any one help me please :))
I just was tired at night yesterday, this is stupid, but for show "cc" in control panel, we need to use not 'TextTrackDisplay' component but 'SubtitlesButton'.

Cant access objects within state

I have a component that uses axios to access the PubMed api (in componentDidMount), retrieves some publication ids then stores them in state as "idlist". A second function is then called (addPapers) which passes in this id list and makes a second api call to retrieve further details (title, journal, authors) for each id. All this seems to work fine and when I use react tools to check state there is an array ("paperList") full of objects that have the expected key:value pairs. However, when I try to map over this array and access the values within the objects in the render function (ie paper.title, paper.author, paper.journal) they are returning as undefined. I haven't been using react for long and suspect I am making a basic mistake but cant figure it out.
I have tried console.logging each step and the expected data is in state and correct in react tools
import axios from 'axios'
import './App.css';
import rateLimit from 'axios-rate-limit';
class App extends Component {
state= {
idlist: [],
papersList : ""
}
componentDidMount () {
console.log("incomponent")
axios.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=1000&term=((Australia%5Bad%5D)%20AND%20(%222019%2F07%2F01%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D))%20AND%20(%22nature%22%5BJournal%5D%20OR%20%22Nature%20cell%20biology%22%5BJournal%5D%20OR%20%22Nature%20structural%20%26%20molecular%20biology%22%5BJournal%5D)")
.then (response =>
this.setState({idlist: response.data.esearchresult.idlist}, () => {
this.addPapers(this.state.idlist)
}
)
)}
addPapers = (idlist) => {
if (idlist) {
const http = rateLimit(axios.create(), { maxRequests: 6, perMilliseconds: 1000 })
const list = this.state.idlist.map(id => {
let paperObj ={};
let paperList =[]
http.get(`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=${id}&api_key=9476810b14695bd14f228e63433facbf9c08`)
.then (response2 => {
const title = response2.data.result[id].title
const journal = response2.data.result[id].fulljournalname
const authorList = []
const authors = response2.data.result[id].authors
authors.map((author, idx) =>
idx > 0 ? authorList.push(" " + author.name) : authorList.push(author.name))
paperObj.title = title
paperObj.journal = journal
paperObj.authors = authorList.toString()
paperList.push(paperObj)
})
return paperObj
})
this.setState({papersList: list})
}
}
render () {
let article = ""
if (this.state.papersList.length){
article = this.state.papersList.map(paper =>
console.log (paper.title)
console.log (paper.authors)
console.log (paper.journal)
)
}
return (
<div className="App">
<h1>Publications</h1>
{article}
</div>
);
}
}
export default App;
I expect that when I map over paperList and extract each paper I should be able to return the title, journal or authors using console.log(paper.title), console.log(paper.title), console.log(paper.title). These are all returning undefined.
You have two issues in code
1) paperList array declaration should be out of map loop.
2) paperList should be returned instead of paperObj
Working code below make some enhancements in render function
Also codesandbox link
import React from "react";
import ReactDOM from "react-dom";
import rateLimit from "axios-rate-limit";
import axios from "axios";
import "./styles.css";
class App extends React.Component {
state = {
idlist: [],
papersList: ""
};
componentDidMount() {
console.log("incomponent");
axios
.get(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=1000&term=((Australia%5Bad%5D)%20AND%20(%222019%2F07%2F01%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D))%20AND%20(%22nature%22%5BJournal%5D%20OR%20%22Nature%20cell%20biology%22%5BJournal%5D%20OR%20%22Nature%20structural%20%26%20molecular%20biology%22%5BJournal%5D)"
)
.then(response =>
this.setState({ idlist: response.data.esearchresult.idlist }, () => {
this.addPapers(this.state.idlist);
})
);
}
addPapers = idlist => {
if (idlist) {
const http = rateLimit(axios.create(), {
maxRequests: 6,
perMilliseconds: 1000
});
let paperList = [];
this.state.idlist.forEach(id => {
let paperObj = {};
http
.get(
`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=${id}&api_key=9476810b14695bd14f228e63433facbf9c08`
)
.then(response2 => {
const title = response2.data.result[id].title;
const journal = response2.data.result[id].fulljournalname;
const authorList = [];
const authors = response2.data.result[id].authors;
authors.map((author, idx) =>
idx > 0
? authorList.push(" " + author.name)
: authorList.push(author.name)
);
paperObj.title = title;
paperObj.journal = journal;
paperObj.authors = authorList.toString();
paperList.push(paperObj);
})
.then(result => {
this.setState({ papersList: paperList });
});
});
}
};
render() {
return (
<div className="App">
<h1>Publications</h1>
{this.state.papersList.length &&
this.state.papersList.map(data => {
return <div>{data.title}</div>;
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hope it helps!!!
Do it like this:
render () {
let article;
if (this.state.papersList.length){
article = this.state.papersList.map(paper => <p>span>Title is {paper.title}</span></p> )
}
return (
<div className="App">
<h1>Publications</h1>
{article}
</div>
);
}

How to open a page in new tab on click of a button in react? I want to send some data to that page also

I'm working on a raise invoice page, in which user can raise a invoice on clicking of a button, I would call a api call and after getting the response I want to send some data to a page(RaisedInvoice.jsx) which should open in a new tab, how can i do it. The thing which I am not getting is how to open a page in new tab on click of a button in ReactJs.
RaiseInvoice.jsx:
import React from 'react';
import Links from './Links.jsx';
import history from './history.jsx';
import axios from 'axios';
class RaiseInvoice extends React.Component {
constructor(props) {
super(props);
// This binding is necessary to make `this` work in the callback
this.state = {projects: [], searchParam : ''};
this.raiseInvoiceClicked = this.raiseInvoiceClicked.bind(this);
}
raiseInvoiceClicked(){
// here i wish to write the code for opening the page in new tab.
}
render() {
return (
<div>
<Links activeTabName="tab2"></Links>
<div className="container">
<div className = "row col-md-4">
<h1>Raise Invoice...</h1>
</div>
<div className = "row col-md-4"></div>
<div className = "row col-md-4" style ={{"marginTop":"24px"}}>
<button type="button" className="btn btn-default pull-right" onClick={this.raiseInvoiceClicked}>Raise Invoice</button>
</div>
</div>
</div>
)
}
}
export default RaiseInvoice;
Since you were going to send big data, appending them to your target URL looks shabby. I would suggest you use 'LocalStorage' for this purpose. So your code looks like this,
raiseInvoiceClicked(){
// your axios call here
localStorage.setItem("pageData", "Data Retrieved from axios request")
// route to new page by changing window.location
window.open(newPageUrl, "_blank") //to open new page
}
In your RaisedInvoice.jsx, retrieve the data from Local Storage like this,
componentWillMount() {
localStorage.pagedata= "your Data";
// set the data in state and use it through the component
localStorage.removeItem("pagedata");
// removing the data from localStorage. Since if user clicks for another invoice it overrides this data
}
You can just use plain JS to do it and append some query perimeters with it
raiseInvoiceClicked(){
const url = 'somesite.com?data=yourDataToSend';
window.open(url, '_blank');
}
Instead of calling raiseInvoiceClicked() function inside onclick method, you can try
onClick="window.open('your_url')"
in your code.
Simply do this!
const openLinkInNewTab = ( url ) => {
const newTab = window.open(url, '_blank', 'noopener,noreferrer');
if ( newTab ) newTab.opener = null;
}
//...
return (
//...
<button onClick={ () => openLinkInNewTab('your.url')}> Click Here </button>
//...
)
You can open it in a new window using the following code.
Please note that for props you can pass any child components that should be rendered inside new window.
const RenderInWindow = (props) => {
const [container, setContainer] = useState(null);
const newWindow = useRef(null);
useEffect(() => {
// Create container element on client-side
setContainer(document.createElement("div"));
}, []);
useEffect(() => {
// When container is ready
if (container) {
// Create window
newWindow.current = window.open(
"",
"",
"width=600,height=400,left=200,top=200"
);
// Append container
newWindow.current.document.body.appendChild(container);
// Save reference to window for cleanup
const curWindow = newWindow.current;
// Return cleanup function
return () => curWindow.close();
}
}, [container]);
return container && createPortal(props.children, container);
};
Pass this data with props:
let href = '...url_to_redirect...'; let data_to_send = [...];
let api_href = '...url_api_where_sent_data.../?data_to_send';
export const DictDefaultOptions = (url=(api_href), method='GET') => {
let defaultOptions = {
url : url,
method : method,
mode : 'cors',
headers : {'Access-Control-Allow-Origin':'*'}
};
return defaultOptions };
let sentData = {
method: defaultOptions.method,
mode: defaultOptions.mode
};
send_data_to_api = () => {
let api_return = await fetch(api_href, sentData)
.then(response => response.json())
.then(responseText => {
data = (JSON.parse(JSON.stringify(responseText)))
})
.catch(error => {
console.log(`${requestURL} error: `, error)
});
do { await this.wait(100) } while(Object.keys(api_return).length <= 0);
if (Object.keys(api_return).length > 0) {
return window.open(href, "_blank")
}
};

Resources