3 votes

Comment implémenter l'API JS de Google Maps dans React sans bibliothèque externe ?

J'essaie de me familiariser avec la mise en œuvre d'API externes dans React et je souhaite pouvoir utiliser l'API de Google Maps pour afficher une carte dans un composant enfant. Idéalement, je veux comprendre comment faire cela sans aucune bibliothèque externe pour avoir une compréhension fondamentale du processus avant d'utiliser quelque chose comme Axios.

Ma question est la suivante : comment puis-je utiliser l'extrait suivant de la documentation de Google pour l'API dans React ?

<script async defer
      src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDZfVO29Iytspv4xz7S68doIoiztiRLhbk&callback=initMap'>
</script>

J'ai essayé de l'utiliser dans mon fichier index.html mais lorsque je fais référence à l'objet google dans un composant enfant de React, j'obtiens une erreur :

./src/Main.js Ligne 114 : 'google' n'est pas défini no-undef

Même si ce n'est pas la façon la plus élégante de procéder, une compréhension de base sur la façon d'implémenter l'API sans bibliothèque externe serait grandement appréciée. Nous vous remercions.

EDIT :

Mon App.js :

import React, { Component } from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import Main from './Main';

import logo from './logo.svg';
import './App.css';
import injectTapEventPlugin from 'react-tap-event-plugin';

injectTapEventPlugin();

class App extends Component {
  render() {
    return (
      <MuiThemeProvider>
        <Main />
      </MuiThemeProvider>
    );
  }
}

export default App;

Mon Main.js :

import React, { Component } from 'react';
import { FlatButton, Dialog, Card, Drawer, Paper, AppBar, Popover, Menu, MenuItem } from 'material-ui';

var items = [
    {
        id: 0,
        name: 'Test 1',
        city: 'Toronto',
        longitude: 24.42142422,
        latitude: 49.24121415,
        tags: ['vegan', 'cheap', 'low-calorie'],
        reviews: [
            {
                rating: 5,
                reviewText: 'This was an amazing restaurant. Incredibly fast service, a large variety of options, and delicious food. I\'ll be here often',
                author: 'Mohammad Sheikh',
                date: new Date(),
                helpfulCount: 5,
                notHelpfulCount: 4
            },
            {
                rating: 2,
                reviewText: 'Absolutely horrible. Please stop making food.',
                author: 'Dissatisfied Customer',
                date: new Date(),
                helpCount: 2,
                notHelpfulCount: 3
            },
        ],
        foods: 
        [
            {
                id: 0,
                name: 'Salad',
                img: 'http://www.images.google.com/',
                tags: ['vegan', 'low-calorie', 'cheap'],
                nutrition: 
                {
                    calories: 300,
                    fat: 5,
                    carbs: 40,
                    protein: 24
                },
                reviews: 
                {
                    rating: 4,
                    reviewText: 'Decent salad. Would recommend.',
                    author: 'Vegan Bro',
                    date: new Date(),
                    helpCount: 4,
                    notHelpfulCount: 1
                }  
            },
            {
                id: 1,
                name: 'Pasta',
                img: 'http://www.images.google.com/',
                tags: ['vegetarian', 'dinner'],
                nutrition: 
                {
                    calories: 800,
                    fat: 40,
                    carbs: 80,
                    protein: 20
                },
                reviews: 
                {
                    rating: 5,
                    reviewText: 'Absolutely amazing',
                    author: 'Food Fan',
                    date: new Date(),
                    helpCount: 8,
                    notHelpfulCount: 4
                }  
            },
        ],
    },
];

const paperStyle = {
  height: 100,
  width: 100,
  margin: 20,
  textAlign: 'center',
  display: 'table',
  position: 'relative',
  clear: 'both',
  float: 'right',
  zIndex: 6
};

const paperContent = {
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)'
}

class RestaurantDialog extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            open: false
        }
    }

    render() {
        return (
            <Dialog>
            </Dialog>
        )
    }
}

class RestaurantButton extends React.Component {
    constructor(props) {
        super(props);
    }

    handleClick = () => {

    }

    render() {
        return (
            <FlatButton style={{width: '100%', height: '64px'}} onClick>
                {this.props.item.city}
                <RestaurantDialog restaurant={this.props.item.name} />
            </FlatButton>
        )
    }
}

class MapComponent extends React.Component {

    constructor(props) {
        super(props);
        this.googleChecker = this.googleChecker.bind(this);
        this.renderMap = this.renderMap.bind(this);
    }

    googleChecker() {
        if (!window.google.maps) {
            setTimeout(this.googleChecker, 100);
        }
        else {
            this.renderMap();
        }
    }

    renderMap() {
        var map = google.maps.Map(document.getElementById('map'), {
            zoom: 4,
            center: {lat: 0, lng: 0}
        });
    }

    componentDidMount() {
        this.googleChecker();
    }

    render() {

        const selections = this.props.currentSelections;
        const buttons = items.filter((item) => {
            for (let i = 0; i < selections.length; i++) {
                if (selections.map((selection) => {return selection.toLowerCase()}).indexOf(item.tags[i].toLowerCase()) > -1) {
                    return true;
                }
            }}).map((item) => {
                return (
                    <RestaurantButton style={{zIndex: '5'}} item={item} />
                )
            });

        return (
            <Paper id='map' zDepth={3} style={{height: '300px', width: '100%', backgroundColor: 'white', position: 'absolute'}}>
                { buttons }
            </Paper>
        )
    }
}

class SelectionIcon extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <Paper circle={true} zDepth={5} style={this.props.style} key={this.props.index} onClick={this.props.close} >
                <div style={paperContent}>{this.props.item}</div>
            </Paper>
        )
    }
}

class SelectionIcons extends React.Component {
    constructor(props) {
        super(props);

    }

    handleSelectionClose = (e) => {
        e.currentTarget.open = false;
    }

    render() {

    let currentSelections = this.props.currentSelections.slice();
    let list = currentSelections.map((item, i) => {
        return (
            <Paper circle={true} zDepth={5} style={paperStyle} key={i} onClick={this.handleSelectionClose}>
                <div style={paperContent}>{item}</div>
            </Paper>
        )
    });

        return (
            <div>
                {list}
            </div>
        )
    }
}

class Main extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            navMenuOpen: false,
            currentSelections: []
        }

    }

    handleMenuButtonTouch = (e) => {
        this.setState({
            anchorEl: e.currentTarget.parentNode,
            navMenuOpen: !this.state.navMenuOpen
        })
    }

    handleRequestChange = (change) => {
        this.setState({
            navMenuOpen: change.open
        })
         console.log(document.getElementById('test').style);
    }

    handleMenuClick = (e) => {
        let currentSelections = this.state.currentSelections.slice();
        if (currentSelections.indexOf(e) > -1) {
            currentSelections.splice(currentSelections.indexOf(e), 1);
        }
        else {
            currentSelections.push(e);
        }
        console.log(currentSelections)
        this.setState({ currentSelections });
     }

    render() {
        return (
            <div>
                <AppBar title='The App' id='test' zDepth={1} onLeftIconButtonTouchTap={this.handleMenuButtonTouch} style={{zIndex: 4}}> 
                </AppBar>
                <Drawer 
                        id='test2'
                        open={this.state.navMenuOpen}
                        onRequestChange={() => {this.handleRequestChange;}}
                        containerStyle={{zIndex: 3, marginTop: '64px'}}>

                        <Menu>
                            <MenuItem primaryText='High Protein' onClick={() => this.handleMenuClick('High Protein')} />
                            <MenuItem primaryText='Vegetarian' onClick={() => this.handleMenuClick('Vegetarian')} />
                            <MenuItem primaryText='Vegan' onClick={() => this.handleMenuClick('Vegan')} />
                        </Menu>
                    </Drawer> 
                <MapComponent items={items} currentSelections={this.state.currentSelections} />
                <SelectionIcons currentSelections={this.state.currentSelections} />  
            </div>
        )
    }
}

export default Main;

Mon index.html :

<!doctype html>
<html lang="en">
  <head>  
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <script async defer
      src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDZfVO29Iytspv4xz7S68doIoiztiRLhbk'>
    </script>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

4voto

Rodrigo Points 66

Le problème est lié au fonctionnement de l'asynchronisme et du report lors de l'utilisation de l'API Google Maps.

En fait, l'API de Google n'est pas encore chargée lorsque votre code atteint le point où la carte doit être rendue. Veuillez consulter cet article pour comprendre comment cela fonctionne :

https://stackoverflow.com/a/36909530/2456879

Il existe deux solutions.

SOLUTION UN N'utilisez pas async et defer dans votre balise script afin de permettre au script d'être téléchargé avant d'exécuter votre application :

<script src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDZfVO29Iytspv4xz7S68doIoiztiRLhbk&callback=initMap'>
</script>

SOLUTION DEUX Créez une sorte de vérificateur récursif pour voir si l'api google a été chargé afin de continuer l'exécution de votre application une fois l'api google maps disponible :

class MyMap extends Component{

  constructor(props){
    super(props);
    this.googleChecker = this.googleChecker.bind(this);
    this.renderMap = this.renderMap.bind(this);
  }

  googleChecker() {
    // check for maps in case you're using other google api
    if(!window.google.maps) {
      setTimeout(googleChecker, 100);
      console.log("not there yet");
    } else {
      console.log("we're good to go!!");
      // the google maps api is ready to use, render the map
      this.renderMap();
    }
  }

  renderMap(){
    const coords = { lat: 41.375885, lng: 2.177813 };
    // create map instance
    new google.maps.Map(this.refs.mapContainer, {
      zoom: 16,
      center: {
        lat: coords.lat,
        lng: coords.lng
      }
    });
  }

  componentDidMount(){
    this.googleChecker();
  }

  render(){
    return(
      <div className="card map-holder">
        <div className="card-block" ref="mapContainer" />
      </div>
    );
  }
}

Vous pouvez également utiliser une promesse et la résoudre dans la méthode du vérificateur ou quelque chose de similaire. Vous pouvez également placer ce code dans un composant parent, stocker un booléen dans l'état et le transmettre aux composants enfants afin de lancer le rendu de la ou des cartes une fois l'API disponible. Cette approche peut également être utilisée avec redux et redux thunk afin de résoudre une promesse. Comme vous pouvez le constater, il existe plusieurs alternatives en fonction de votre approche.

Voici un échantillon en direct utilisant le vérificateur de délai d'attente :

https://jsbin.com/tejutihoka/edit?js,output

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X