Load Google Place API in Gatsbyjs (Reactjs) project - reactjs

I am trying to use the AutoComplete address service from Google Place API.
Found this library:
https://github.com/kenny-hibino/react-places-autocomplete#load-google-library
It asks for loading the library in my project:
https://github.com/kenny-hibino/react-places-autocomplete#getting-started
I would do it in the public/index.html if it's pure Reactjs project. However, the public/index.html in Gatsbyjs project will be deleted and re-generated every time when running:
Gatsby develop
command line.
How can I use the Google Place API in my Gatsbyjs project?
Update
I have tried 2 ways to achieve this.
Use React-Helmet in /layouts/index.js , here is how it looks like:
<Helmet>
<script src="https://maps.googleapis.com/maps/api/js?key={API}&libraries=places&callback=initAutocomplete" async defer></script>
</Helmet>
Put the script reference in the /public/index.html, which looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charSet="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title data-react-helmet="true"></title>
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={API_KEY}&libraries=places" async defer ></script>
</head>
<body>
<div id="___gatsby"></div>
<script src="/commons.js"></script>
</body>
</html>
For the 1st solution, every time after I refresh my page, the project throws an error asking for loading the Google JavaScript Map API.
For the 2nd solution, every time after I re-start the Gatsby by the command line: gatsby develop
it re-generates the index.html which flushes away my JavaScript reference in it.

You shouldn't modify any files in the public forlder with GatsbyJS.
Instead, I recommend you to customize your html.js file.
To do so, first run:
cp .cache/default-html.js src/html.js
You should have the html.js file in /src/html.js.
Now you can put your <script> tag within the <head>.

Update Feb 24, 2020
Here's a more modern implementation using React hooks with some performance optimizations based on React.memo and a custom shouldUpdate function. See this blog post for details.
import { functions, isEqual, omit } from 'lodash'
import React, { useState, useEffect, useRef } from 'react'
function Map({ options, onMount, className, onMountProps }) {
const ref = useRef()
const [map, setMap] = useState()
useEffect(() => {
// The Map constructor modifies its options object in place by adding
// a mapTypeId with default value 'roadmap'. This confuses shouldNotUpdate.
// { ...options } prevents this by passing in a copy.
const onLoad = () =>
setMap(new window.google.maps.Map(ref.current, { ...options }))
if (!window.google) {
const script = document.createElement(`script`)
script.src = `https://maps.googleapis.com/maps/api/js?key=` + YOUR_API_KEY
document.head.append(script)
script.addEventListener(`load`, onLoad)
return () => script.removeEventListener(`load`, onLoad)
} else onLoad()
}, [options])
if (map && typeof onMount === `function`) onMount(map, onMountProps)
return (
<div
style={{ height: `60vh`, margin: ` 1em 0`, borderRadius: ` 0.5em` }}
{...{ ref, className }}
/>
)
}
function shouldNotUpdate(props, nextProps) {
const [funcs, nextFuncs] = [functions(props), functions(nextProps)]
const noPropChange = isEqual(omit(props, funcs), omit(nextProps, nextFuncs))
const noFuncChange =
funcs.length === nextFuncs.length &&
funcs.every(fn => props[fn].toString() === nextProps[fn].toString())
return noPropChange && noFuncChange
}
export default React.memo(Map, shouldNotUpdate)
Map.defaultProps = {
options: {
center: { lat: 48, lng: 8 },
zoom: 5,
},
}
Old Answer
Using html.js
Modifying src/html.js like so (as Nenu suggests) is one option.
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class HTML extends Component {
render() {
return (
<html {...this.props.htmlAttributes}>
<head>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{this.props.headComponents}
</head>
<body {...this.props.bodyAttributes}>
{this.props.preBodyComponents}
<div
key={`body`}
id="___gatsby"
dangerouslySetInnerHTML={{ __html: this.props.body }}
/>
{this.props.postBodyComponents}
// MODIFICATION // ===================
<script
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"
async
defer
/>
// ===================
</body>
</html>
)
}
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
}
Then you can access the Google Maps API anywhere in your project from window.google.maps.(Map|Marker|etc.).
The React way
To me that felt a little anachronistic, though. If you want a reusable React component that you can import into any page or template as import Map from './Map', I suggest this instead. (Hint: See update below for equivalent function component.)
// src/components/Map.js
import React, { Component } from 'react'
export default class Map extends Component {
onLoad = () => {
const map = new window.google.maps.Map(
document.getElementById(this.props.id),
this.props.options
)
this.props.onMount(map)
}
componentDidMount() {
if (!window.google) {
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`
const headScript = document.getElementsByTagName('script')[0]
headScript.parentNode.insertBefore(script, headScript)
script.addEventListener('load', () => {
this.onLoad()
})
} else {
this.onLoad()
}
}
render() {
return <div style={{ height: `50vh` }} id={this.props.id} />
}
}
Use it like so:
// src/pages/contact.js
import React from 'react'
import Map from '../components/Map'
const center = { lat: 50, lng: 10 }
const mapProps = {
options: {
center,
zoom: 8,
},
onMount: map => {
new window.google.maps.Marker({
position: center,
map,
title: 'Europe',
})
},
}
export default function Contact() {
return (
<>
<h1>Contact</h1>
<Map id="contactMap" {...mapProps} />
</>
)
}

What woked for me was to create a gatsby-ssr.js file in the root of my project, and then include the script there, like this:
import React from "react"
export function onRenderBody({ setHeadComponents }) {
setHeadComponents([
<script
key="abc"
type="text/javascript"
src={`https://maps.googleapis.com/maps/api/js?key=${process.env.GATSBY_API_KEY}&libraries=places`}
/>,
])
}
Don't forget to include GATSBY_API_KEY or whatever you want to call it in your .env.development and .env.production files:
GATSBY_API_KEY=...

Related

Load external script using JavaScript in Next.js

A while ago I made a relatively massive app for a single developer for my own personal use - a "social media" of sorts. I wrote it in React.js
I have recently decided to revisit the app and revamp it - primarily make it server-side rendered using Next.js.
The key problem is loading the themes. I had a feature where I would programmatically add an external script tag to the end of the body which would manipulate the canvas and show some animations.
I had no issues when I worked with React, as everything got loaded the way I expected it to, but when it comes to Next, that simply isn't the case. The script tag loads, but the code doesn't get executed.
The most important files I have issues with are:
My _document.jsx file:
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { BASE_URL as base } from '../config'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html lang={"en"}>
<Head>
<link rel={"icon"} href={`${base}/short.png`} />
<link
rel={"apple-touch-icon"}
href={`${base}/apple-icon-180x180-dunplab-manifest-34821.png`}
/>
<link
href="https://fonts.googleapis.com/css?family=Roboto&display=swap"
rel={"stylesheet"}
/>
<link
rel={"stylesheet"}
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css"
integrity={"sha256-zmfNZmXoNWBMemUOo1XUGFfc0ihGGLYdgtJS3KCr/l0="}
crossOrigin={"anonymous"}
/>
<link
rel={"stylesheet"}
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity={"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"}
crossOrigin={"anonymous"}
/>
<script
defer
src={`${base}/Vector2.js`}
charSet={"utf-8"}
></script>
<link rel={"manifest"} href={`${base}/manifest.json`} />
</Head>
<body>
<div className="canvas-wrapper">
<canvas id="canvas" className="canvas-bg"></canvas>
<NextScript />
</div>
<Main>
<script
defer
src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity={"sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"}
crossOrigin={"anonymous"}
></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js"
integrity={"sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"}
crossOrigin={"anonymous"}
></script>
<script
defer
src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity={"sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"}
crossOrigin={"anonymous"}
></script>
</Main>
</body>
</Html>
)
}
}
export default MyDocument
My _app.jsx file:
import '../styles/globals.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import { ThemeProvider } from "../contexts/ThemeContext";
import { FontProvider } from "../contexts/FontContext";
import { DevProvider } from "../contexts/DevContext";
import { LanguageProvider } from "../contexts/LanguageContext";
// import { SocketProvider } from "../contexts/SocketContext";
import { ColourProvider } from "../contexts/ColourContext";
import PageContent from '../components/layout/PageContent/PageContent';
import { useStore } from '../store';
import { useEffect } from "react"
import { Provider } from 'react-redux';
import Head from 'next/head';
import { BASE_URL as base } from '../config';
import Nav from "../components/layout/Nav/Nav"
import setAuthToken from '../utils/setAuthToken';
import { getUser } from '../actions/auth';
function MyApp({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState);
useEffect(() => {
if (localStorage.getItem('token')) {
setAuthToken(localStorage.getItem('token'))
}
store.dispatch(getUser())
})
return (
<>
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta property="og:title" content="Impulse" />
<meta property="og:description" content="Make an impact. Change minds." />
<meta
property="og:image"
content={`${base}/favicon-96x96-dunplab-manifest-34821.png`}
/>
<meta
name="description"
content="Welcome to Impulse - make an impact, change minds. Impulse is dedicated to your enjoyment and pleasure!"
/>
<script defer src={`${base}/scripts/initlog.js`}></script>
<script defer src={`${base}/scripts/blurfocus.js`}></script>
<title>Impulse</title>
</Head>
<ColourProvider>
<DevProvider>
<FontProvider>
{/* <SocketProvider> */}
<ThemeProvider>
<LanguageProvider>
<Provider store={store}>
<PageContent>
<Nav />
<Component {...pageProps} />
</PageContent>
</Provider>
</LanguageProvider>
</ThemeProvider>
{/* </SocketProvider> */}
</FontProvider>
</DevProvider>
</ColourProvider>
</>
)
}
export default MyApp
My useScript.js hook
import { useRef } from "react"
import { BASE_URL as base } from "../config"
// actually, it's supposed to load a single script, not more of them
const useScripts = (script) => {
const scriptLoaded = useRef(false);
if (typeof window !== "undefined" && !scriptLoaded.current && script) {
const element = document.createElement("script");
element.src = `${base}/static/canvasThemes/${script}.js`;
element.type = "text/javascript";
const position = document.querySelector("head");
position.appendChild(element);
scriptLoaded.current = true;
}
};
export default useScripts
My PageContent.jsx file, which was previously referenced in the _app.jsx file:
import React, { useContext, useEffect, useState } from "react";
import { ThemeContext } from "../../../contexts/ThemeContext";
import { FontContext } from "../../../contexts/FontContext";
import { ColourContext } from "../../../contexts/ColourContext";
import StyledPageContent from '../../../styled/StyledPageContent';
import useScripts from "../../../hooks/useScript";
import cookie from 'cookie-cutter'
function PageContent(props) {
const { isDarkTheme, toggleTheme } = useContext(ThemeContext);
const { isLegacyFont } = useContext(FontContext);
const { colour } = useContext(ColourContext)
const [theme, setTheme] = useState("")
useEffect(() => {
const newOne = cookie.get("isDarkTheme")
console.log("NJUUAN _APP", newOne);
setTheme(() => newOne)
}, [])
useScripts(theme)
return (
<StyledPageContent
isDarkTheme={isDarkTheme}
isLegacyFont={isLegacyFont}
colour={colour}
>
{props.children}
</StyledPageContent>
);
}
export default PageContent;
I'm looking for a way to add a script programmatically and immediately execute it.
Important: ALL script tags I want to add are considered "themes", which would grab the canvas from the _document and manipulate its background colour/fill/add text etc.
I have been trying a million different things for a month now and am starting to lose hope.
Not sure if I'll get any help, but thanks in advance all the same!

Loading p5js of react-p5 npm package to NextJS app shows "ReferenceError: window is not defined"

This is my Code Which I got from react-p5 typescript Example and modified it a bit
import Sketch from "react-p5";
import p5Types from "p5";
type InputParameterType = {};
function P5JsComponent({}: InputParameterType) {
let x = 50;
const y = 50;
//See annotations in JS for more information
const setup = (p5: p5Types, canvasParentRef: Element) => {
p5.createCanvas(500, 500).parent(canvasParentRef);
};
const draw = (p5: p5Types) => {
p5.background(0);
p5.ellipse(x, y, 70, 70);
x++;
};
return <Sketch setup={setup} draw={draw} />;
}
export default P5JsComponent;
My Parent Component in My NextJs App is 'homepage.tsx' which is present in the pages directory.
import Head from "next/head";
import P5JsComponent from "#/components/P5JsComponent";
function homepage() {
return (
<div>
<Head>
<title>My App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<P5JsComponent />
</div>
);
}
export default homepage;
I am getting a ReferenceError: window is not defined error when I run this code.
In server-side-rendering, we haven't global variables from the browser, like the "window" variable.
P5JsComponent must be rendered on the client-side.
Import P5JsComponent with no SSR:
const P5JsComponent = dynamic(
() => import("#/components/P5JsComponent"),
{ ssr: false }
)
ref: https://nextjs.org/docs/advanced-features/dynamic-import

Error: "MyDocument.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string

My project is in https://github.com/Talita1996/NLW4
I created a project with the command yarn create next-app project_name
I changed the extension of some files and added some code
I installed typescript with the command yarn add typescript #types/react #types/react-dom #types/node -D
On the first day everything worked fine, but today, when I call yarn dev I get the error:
in your page/_document you need to extend Document class
import Document, { Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght#400;500;600&family=Rajdhani:wght#600&display=swap" rel="stylesheet" />
</Head>
<body>
<Main/>
<NextScript/>
</body>
</Html>
);
}
}
Here's a solution using a function component, and fully typed with TypeScript.
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
import type { DocumentContext, DocumentInitialProps } from 'next/document'
const Document = () => {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
Document.getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => {
const initialProps = await NextDocument.getInitialProps(ctx)
return { ...initialProps }
}
export default Document
As pointed out in previous answers, you have to call await Document.getInitialProps(ctx) and spread the returned object in the getInitialProps's return statement.
Please check if you have missed 'await' and then ad it :
static async getInitialProps(ctx){
const initialProps = await Document.getInitialProps(ctx)
return {...initialProps, locale: ctx?.locale || "fr"}
}

How to use WebView to show React jsx file

I can't see any support to React jsx local file. How can I use them in WebView ? Can someone help me .
I'm using in this case like this:
<WebView
source={{
uri: isAndroid
? 'file:///android_asset/android_asset/index.jsx'
: 'index.jsx'
}}
allowUniversalAccessFromFileURLs={true}
onShouldStartLoadWithRequest={request => {
return true
}}
useWebKit={false}
/>
UPDATE
I tried for hours but I can't find the way how to import React file to Javascript.
this is my react file like:
index.jsx
import * as React from 'react';`
export class TVChartContainer extends React.PureComponent {
componentDidMount(){ // Do something}
render(){
return (
<div
id={this.props.containerId}
className={'TVChartContainer'}
/>
);
}
}
You need to get HTML string. In WEB React you can render JSX by ReactDom but in ReactNative you can't do it.
The easiest way is generating html string according to your jsx and data. You can put everthing there. I show you several examples.
class MyInlineWeb extends Component {
componentDidMount(){ // Do something}
renderHtmlAndroid = (data) => {
return `<div>
<style>
.block {}
</style>
<div class="block">${data}<div>
<div>`
}
renderHtmliOs = (data) => {
return `<!DOCTYPE html>
<html>
<head>
<title>Title</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app">${data}</div>
</body>
</html>`
}
render() {
return (
<WebView
originWhitelist={['*']}
source={{html: isAndroid ? this.renderHtmlAndroid([]) : this.renderHtmliOs([])}}
/>
);
}
}

Botframework with Reactjs is not working on IE11

botframework with react is not working in IE,
I'm using my index html file similar to
https://github.com/microsoft/BotFramework-WebChat/tree/master/samples/03.a.host-with-react, its working in chrome but not in IE, i tried using webchat-es5.js also.
I'm using token given by bot team.
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Web Chat: Integrate with React</title>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/react#16.5.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.5.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-redux#5.0.7/dist/react-redux.min.js"></script>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat-es5.js"></script>
<style>
html, body { height: 100% }
body { margin: 0 }
#webchat {
height: 100%;
width: 100%;
}
</style>
</head>
<div id="webchat" role="main"></div>
<script type="text/babel">
function start() {
const { createStore, ReactWebChat } = window.WebChat;
const { Provider } = window.ReactRedux;
const store = createStore();
window.ReactDOM.render(
<Provider store={ store }>
<ReactWebChat
directLine={ window.WebChat.createDirectLine({ token:'mytoken' }) }
storeKey="webchat"
/>
</Provider>,
document.getElementById('webchat')
);
document.querySelector('#webchat > *').focus();
}
start();
</script>
</body>
</html>
working in Chrome but not in IE, somebody help me on this please.
Unfortunatley, the "webchat-es5.js" package was designed for instantiating web chat via the window.WebChat.renderWebChat method. While the "webchat.js" package does allow for using window.ReactDOM.render, it is not designed for older browsers, such as IE.
I played with this a bunch and was simply unable to render web chat using window.ReactDOM.render while also in IE, despite utilizing any number of polyfills. That being said, I was able to get the hosted React web chat sample to work in a proper React project with a few of modifications. Please note, this also makes use of webpack.
Hope of help!
index.js: Nothing special or unexpected here.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import './css/index.css';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
app.js: Just some necessary routing.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import WebChatView from './webChatView';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Route path="/" exact component={WebChatView} />
</div>
</Router>
);
}
}
export default App;
webChatView.js: Renders the web chat page (with some necessary styling)
import React, { Component } from 'react';
import WebChatView from './webchatView';
const FragmentStyling = {
'container': {
'display': 'flex',
'justifyContent': 'center'
},
'div': {
'height': '40rem',
'minHeight': '20rem',
'width': '1200px',
'display': 'flex',
'justifyContent': 'space-around',
'marginTop': '2rem'
}
}
class WebChatView extends Component {
render() {
return (
<div style={FragmentStyling.container}>
<div style={FragmentStyling.div}>
<WebChatView id="webchat" role="main" />
</div >
</div>
)
}
}
export default WebChatView;
webchat.js: Several things to note:
Either import '#babel/polyfill' needs to be included or all the 'core-js' imports listed below. Babel recommends importing only the required polyfills (to keep package size down). Those shown below are what is needed. However, using the '#babel' option works, as well.
Simply using fetch as-is breaks due to compatibility issues. There may be other options, but the below 'whatwg-fetch' option works in both IE and Chrome. Others I tested did not.
'fetching' the token needs to be promise-based. Using async/await breaks React web chat in IE.
import 'core-js/es6/map';
import 'core-js/es6/promise'
import 'core-js/es6/set';
import 'core-js/es6/symbol';
import 'core-js/fn/array/find-index';
import 'core-js/fn/array/includes';
import 'core-js/fn/math/sign';
import 'core-js/fn/object/assign';
import 'core-js/fn/string/starts-with';
import { fetch as fetchPolyfill } from 'whatwg-fetch';
import React from 'react';
import ReactWebChat, { createDirectLine } from 'botframework-webchat';
export default class WebChat extends React.Component {
constructor(props) {
super(props);
this.state = {
directLine: null
};
}
componentDidMount() {
this.fetchToken();
}
fetchToken(token) {
fetchPolyfill('http://localhost:3500/directline/token', { method: 'POST' })
.then(res => res.json()) // expecting a json response
.then(json =>
this.setState(() => ({
directLine: createDirectLine(
{
token: json.token
}
)
}))
)
}
render() {
return (
this.state.directLine ?
<ReactWebChat
directLine={this.state.directLine}
/>
:
<div>Connecting to bot…</div>
)
}
}
index.html: The 'react-polyfill.min.js' package needs to be included and should precede any other scripts that might live here.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="//pitzcarraldo.github.io/react-polyfill/react-polyfill.min.js" charSet="UTF-8"></script>
<title>React Web App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
webpack.config.js: The import 'script!react-polyfill' needs to be included at the top of this file.
import 'script!react-polyfill';
const path = require('path');
module.exports = {
entry: ['./src/index.js'],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
mode: 'development'
};

Resources