import * as React from 'react'
import invariant from 'invariant'
import { injectScript } from './utils/injectscript'
import { preventGoogleFonts } from './utils/prevent-google-fonts'
import { isBrowser } from './utils/isbrowser'
import { LoadScriptUrlOptions, makeLoadScriptUrl } from './utils/make-load-script-url'
let cleaningUp = false
interface LoadScriptState {
loaded: boolean
}
export interface LoadScriptProps extends LoadScriptUrlOptions {
id: string
loadingElement?: React.ReactNode
onLoad?: () => void
onError?: (error: Error) => void
onUnmount?: () => void
preventGoogleFontsLoading?: boolean
}
export function DefaultLoadingElement(): JSX.Element {
return
{`Loading...`}
}
export const defaultLoadScriptProps = {
id: 'script-loader',
version: 'weekly',
}
class LoadScript extends React.PureComponent {
public static defaultProps = defaultLoadScriptProps
check: React.RefObject = React.createRef()
state = {
loaded: false,
}
cleanupCallback = (): void => {
delete window.google
this.injectScript()
}
componentDidMount(): void {
if (isBrowser) {
if (window.google && !cleaningUp) {
console.error('google api is already presented')
return
}
this.isCleaningUp()
.then(this.injectScript)
.catch(function err(err) {
console.error('Error at injecting script after cleaning up: ', err)
})
}
}
componentDidUpdate(prevProps: LoadScriptProps): void {
if (this.props.libraries !== prevProps.libraries) {
console.warn(
'Performance warning! LoadScript has been reloaded unintentionally! You should not pass `libraries` prop as new array. Please keep an array of libraries as static class property for Components and PureComponents, or just a const variable outside of component, or somewhere in config files or ENV variables'
)
}
if (isBrowser && prevProps.language !== this.props.language) {
this.cleanup()
// TODO: refactor to use gDSFP maybe... wait for hooks refactoring.
// eslint-disable-next-line react/no-did-update-set-state
this.setState(function setLoaded() {
return {
loaded: false,
}
}, this.cleanupCallback)
}
}
componentWillUnmount(): void {
if (isBrowser) {
this.cleanup()
const timeoutCallback = (): void => {
if (!this.check.current) {
delete window.google
cleaningUp = false
}
}
window.setTimeout(timeoutCallback, 1)
if (this.props.onUnmount) {
this.props.onUnmount()
}
}
}
isCleaningUp = async (): Promise => {
function promiseCallback(resolve: () => void): void {
if (!cleaningUp) {
resolve()
} else {
if (isBrowser) {
const timer = window.setInterval(function interval() {
if (!cleaningUp) {
window.clearInterval(timer)
resolve()
}
}, 1)
}
}
return
}
return new Promise(promiseCallback)
}
cleanup = (): void => {
cleaningUp = true
const script = document.getElementById(this.props.id)
if (script && script.parentNode) {
script.parentNode.removeChild(script)
}
Array.prototype.slice
.call(document.getElementsByTagName('script'))
.filter(function filter(script: HTMLScriptElement): boolean {
return script.src.includes('maps.googleapis')
})
.forEach(function forEach(script: HTMLScriptElement): void {
if (script.parentNode) {
script.parentNode.removeChild(script)
}
})
Array.prototype.slice
.call(document.getElementsByTagName('link'))
.filter(function filter(link: HTMLLinkElement): boolean {
return (
link.href === 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Google+Sans'
)
})
.forEach(function forEach(link: HTMLLinkElement) {
if (link.parentNode) {
link.parentNode.removeChild(link)
}
})
Array.prototype.slice
.call(document.getElementsByTagName('style'))
.filter(function filter(style: HTMLStyleElement): boolean {
return (
style.innerText !== undefined &&
style.innerText.length > 0 &&
style.innerText.includes('.gm-')
)
})
.forEach(function forEach(style: HTMLStyleElement) {
if (style.parentNode) {
style.parentNode.removeChild(style)
}
})
}
injectScript = (): void => {
if (this.props.preventGoogleFontsLoading) {
preventGoogleFonts()
}
invariant(!!this.props.id, 'LoadScript requires "id" prop to be a string: %s', this.props.id)
const injectScriptOptions = {
id: this.props.id,
url: makeLoadScriptUrl(this.props),
}
injectScript(injectScriptOptions)
.then(() => {
if (this.props.onLoad) {
this.props.onLoad()
}
this.setState(function setLoaded() {
return {
loaded: true,
}
})
return
})
.catch(err => {
if (this.props.onError) {
this.props.onError(err)
}
console.error(`
There has been an Error with loading Google Maps API script, please check that you provided correct google API key (${this
.props.googleMapsApiKey || '-'}) or Client ID (${this.props.googleMapsClientId ||
'-'}) to
Otherwise it is a Network issue.
`)
})
}
render(): React.ReactNode {
return (
<>
{this.state.loaded
? this.props.children
: this.props.loadingElement || }
>
)
}
}
export default LoadScript