리액트 - googlemap을 이용한 프로젝트

googlemap 을 이용해서 주변 지역의 맛집을 보여주는 맛지도 프로젝트를 제작한다. 식당의 종류, 평점별로 검색이 가능하며 지도에는 해당 위치에 나타나는 식당의 이미지를 보여줄 것이다.

rapidAPI의 api를 사용해서 구현할 것이다. 사용하는 api는
Travel Adivisor - 식당 관련 정보 API
Open Weather Map API Documentaion - 도시에 따른 날씨 정보 API

프로젝트 생성

vsCode로 빈 폴더에 들어가고 create-reactp-app을 통해 프로젝트를 생성했다. 초기에 존재하는 src 폴더를 지우고 새로운 src 폴더를 생성한뒤 작업을 시작했다.
src/index.js

import React from "react";
import { ReactDOM } from "react";

import App from './App';

ReactDOM.render(<App/>, document.getElementById('root'));

src/App.js

import React from 'react'

const App = () => {
  return (
    <div>
      <h1>hey there!</h1>
    </div>
  )
}

export default App;

이번 프로젝트를 진행하는데 필요한 항목들을 설치했다.

npm install @material-ui/core @material-ui/icons @material-ui/lab @react-google-maps/api axios google-map-react

material-ui : 자주 사용되는 기능/디잔을 component/API로 제공하며, React로 개발을 할 때 다양한 UI제작이 가능하다.

react-google-maps : google-maps기능을 사용하기위한 항목
axios : 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리

google-map-react : 구글 지도 검색창구현을 위한 항목

폴더 구조 생성

페이지 각각의 componenet 파일을 저장할 components폴더를 생성한다.
Header List Map PlaceDetails
그리고 각각의 폴더안에 폴더의 이름과 같은이름의 파일과, css 코드가 들어있는 styles.js 를 추가했다.

간단한 구상

import React from 'react'
import Header from './components/Header/Header';
import List from './components/List/List';
import Map from './components/Map/Map';
import {CssBaseline, Grid} from '@material-ui/core';

const App = () => {
  return (
    <>
    <CssBaseline/>
    <Header/>
    <Grid container spacing={3} style={{width:'100%'}}>
        <Grid item xs={12} md={4}>
            <List />
        </Grid>
        <Grid item xs={12} md={8}>
            <Map />
        </Grid>
    </Grid>
    </>
  )
}

export default App;

maeterial-ui에서 사용하는 Griditem을 보면 xs 와 md가 있는데, xs는 화면을 줄였을때(화면이 작은 기기)의 경우에 차지하는 길이를 뜻하며, md는 그보다 더 큰화면에서 차지하는 비율을 뜻한다. 12가 전체길이이며 xs={12} md={4}라면 화면이 작을때는 해당 item의 너비는 전체 너비와 동일하며, 일반 화면에서는 전체길이의 3분의1로 변경된다라는 뜻이다.

너비를 일정길이이상 줄이면 listmap이 차지하는 전체너비로 변경되어 Map이 아래로 내려가는 모습을 볼수 있다.

Header

왼쪽에는 타이틀, 오른쪽에 검색창을 만들것이다.

import React from 'react'
import {Autocomplete} from '@react-google-maps/api';
import {AppBar, Toolbar, Typography, InputBase, Box} from '@material-ui/core';
import SearchIcon from '@material-ui/icons/Search';
import useStyles from './styles';

const Header = () => {
    const classes = useStyles();
  return (
    <AppBar position="static">
        <Toolbar className={classes.toolbar}>
            <Typography variant="h5" className={classes.title}>
                Travel Advisor
            </Typography>
            <Box display='flex'>
            <Typography variant="h6" className={classes.title}>
                Explore new places
            </Typography>
            {/* <Autocomplete> */}
                <div className={classes.search}>
                    <div className={classes.searchIcon}>
                        <SearchIcon/>
                    </div>
                    <InputBase placeholder="Search..." classes={{root: classes.inputRoot, input:classes.inputInput}} />
                </div>
            {/* </Autocomplete> */}
            </Box>
        </Toolbar>
    </AppBar>
    
  )
}
export default Header

style.js

import {alpha, makeStyles} from '@material-ui/core/styles';
/*xs, extra-small: 0px
sm, small: 600px
md, medium: 900px
lg, large: 1200px
xl, extra-large: 1536px */
export default makeStyles((theme) => ({
    title:{
        display: 'none',
        [theme.breakpoints.up('sm')]:{
            display:'block',
        },
    },
    search: {
        position: 'relative',
        borderRadius: theme.shape.borderRadius,
        backgroundColor: alpha(theme.palette.common.white, 0.15),
        '&:hover':{backgroundColor : alpha(theme.palette.common.white, 0.25)},
        marginRight: theme.spacing(2),
        marginLeft: 0,
        width: '100%',
        [theme.breakpoints.up('sm')]: {marginLeft: theme.spacing(3), width: 'auto'},
    },

    searchIcon: {
        padding: theme.spacing(0,2), 
        height: '100%', 
        position: 'absolute', 
        pointerEvents: 'none', 
        display: 'flex', 
        alignItems: 'center', 
        justifyContent: 'center',
    },

    inputRoot: {
        color: 'inherit',
    },

    inputInput: {
        padding: theme.spacing(1,1,1,0), 
        paddingLeft: `calc(1em + ${theme.spacing(4)}px)`
    },

    toolbar: {
        display: 'flex', 
        justifyContent: 'space-between',
    },

}));

material-ui에서 제공하는 컴포넌트를 사용해서 구현한다.
autocomplete의 경우는 주석처리를 하지 않으면 오류가 나타나기 때문에 해놓은 것이다.

현재 상황

map

지도화면이 나타나는 곳을 작성한다. google-maps를 사용하려면 google project를 만들어서 key 값을 받아와야한다.

https://console.cloud.google.com/projectcreate?pli=1 로 들어가서 원하는 프로젝트 이름을 작성하고 만들기 버튼을 누른뒤 해당 프로젝트로 들어가면


이런 화면이 나타날 것이다. 이때 좌측 메뉴에서 API 및 서비스 - 라이브러리로 들어가서 검색창에 'maps' 라고 검색하면 MAPs JavaScript API라는 항목을 선택하고 '사용'이라는 버튼을 눌러 활성화 시킨다.


버튼을 누른 다음 사용설정된 API목록에서 추가된 MAPs JavaScript API를 클릭하고 좌측 메뉴에서 사용자 인증정보 메뉴를 클릭


가운데 상단에 '사용자 인증정보만들기' > API 키 를 선택하면 화면에 API key 가 나타날 것이다.

map.js

import React from 'react'
import GoogleMapReact from 'google-map-react';
import {Paper, Typography, useMediaQuery} from '@material-ui/core';
import LocationOnOutlineIcon from '@material-ui/icons/LocationOnOutlined';
import Rating from '@material-ui/lab';

import useStyles from './styles';

const Map = () => {
    const classes = useStyles();
    const isMobile = useMediaQuery('(min-width:600px)');
    const cordinates = { lat:0, lng:0};
  return (
        <div className={classes.mapContainer}>
            <GoogleMapReact
                bootstrapURLKeys={{key: 'api 키를 입력'}}
                defaultCenter={cordinates}
                center={cordinates}
                defaultZoom={14}
                margin = {[50, 50, 50, 50]}
                options={''}
                onChange={''}
                onChildClick={''}
            >

            </GoogleMapReact>
        </div>  
    )
}

export default Map

style.js

import {makeStyles} from '@material-ui/core/styles';

export default makeStyles(() => ({
    paper: {
        padding: '10px',
        display: 'flex',
        flexDirection: 'columns',
        justifyContent: 'center',
        width: '100px',
    },
    mapContainer:{
        height: '85vh', 
        width: '100%',
    }
  
}));

LIST

지도화면 옆에서 해당 장소의 상세 정보를 표시해주는 컴포넌트를 작성한다.

먼저 사용자가 장소의 종류와, 평점을 선택하는 Input창을 생성한다.
장소 옵션은 restaurants , hotels, attractions이렇게 3가지로 나누고, 평점은 0, 3점 이상, 4점 이상, 4.5점 이상으로 나누었다.

해당 선택된 값의 상태관리는 useState를 사용했다.

list.js

import React ,{useState} from 'react'
import { CircularProgress, Grid, Typography, InputLabel, MenuItem, FormControl,Select } from '@material-ui/core'

import useStyles from './styles';

const List = () => {
    const classes = useStyles();
    const [type,setType] = useState('restaurants');
    const [rating,setRating] = useState('');
  return (
     <div className={classes.container}>
         <Typography variant='h4'>Restaurants, Hotels & Attractions aound you</Typography>
         <FormControl className={classes.formControl}>
           <InputLabel>Type</InputLabel>
           <Select value={type} onChange={(e) => setType(e.target.value)}>
              <MenuItem value="restaurants">Restaurants</MenuItem>
              <MenuItem value="hotels">Hotels</MenuItem>
              <MenuItem value="attractions">Attractions</MenuItem>             
           </Select>
          </FormControl>
          <FormControl className={classes.formControl}>
           <InputLabel>Rating</InputLabel>
           <Select value={rating} onChange={(e) => setRating(e.target.value)}>
              <MenuItem value={0}>All</MenuItem>
              <MenuItem value={3}>Above 3.0</MenuItem>
              <MenuItem value={4}>Above 4.0</MenuItem>
              <MenuItem value={4.5}>Above 4.5</MenuItem>
           </Select>
          </FormControl>
     </div>
   
  )
}

export default List;

style.js

import {makeStyles} from '@material-ui/core/styles';

export default makeStyles((theme) => ({
    formControl: {
        margin: theme.spacing(1),
        minWidth: 120,
        marginBottom: '30px',
    },

    container: {
        padding: '25px',
    },
    list: {
        height: '75vh', overflow: 'auto',
      },
   
   
    
}))

현재상황

좌측에 옵션을 선택하는 버튼이 나타났다. 이제 장소들의 api로부터 받아오는데 그전에 받아온 데이터를 표시하는 곳을 작성한다. 데이터는 현재 받아오지 않았기 때문에 임의로 작성해서 확인한다.

    const places = [
      {name: 'Cool Places'},
      {name: 'Best Beer'},
      {name: 'Best Steak'},
      {name: 'Cool Places'},
      {name: 'Best Beer'},
      {name: 'Best Steak'},
      {name: 'Cool Places'},
      {name: 'Best Beer'},
      {name: 'Best Steak'},
    ];

list.js에서 FormControl 바로 다음부터 Grid컴포넌트를 작성한다.

 <Grid container spacing={3} className={classes.list}>
              {places?.map((place, i)=> (
                <Grid item key={i} xs={12}>
                  <PlaceDetails place={place}/>
                 </Grid> 
              ))}
          </Grid>

placeDetails.js

import React from 'react'

const PlaceDetails = ({place}) => {
  return (
      <h1>{place.name}</h1>
  )
}

export default PlaceDetails

place 내부 데이터의 name 값을 반복적으로 불러와서 렌더링한다.

API 데이터 가져오기

src 내부에 api 폴더를 만든뒤 index.js 파일을 생성한다. 사용할 api는 RapidAPI에서 Travel Advisor 라는 API를 사용할 것이다. api에서 제공하는 기능을 활용해서 원하는 좌표 지점에 존재하는 식당을 불러오거나, 원하는 영역을 선택하고 그 내부에 있는 장소 정보를 가져올수 있다.

RapidAPI에서 Travel Advisor를 검색해서 사용하기 위해서는 로그인을 해야하므로 참고하자.

왼쪽은 api가 가지고 있는 메소드 목록, 가운데는 선택된 기능에서 내장되어있는 파라미터 정보를 표시하고 오른쪽에서는 해당 파라미터들을 사용하여 만들어진 코드를 보여주고 있다.
나는 한 지점이 아니라 지도 영역을 검색해서 보여주는기능을 원하기 때문에
restaurants > restaurants/list-inboundary를 사용할 것이다.

코드를 복사해보면 아래와 같다

const axios = require("axios");
const API_KEY = process.env.REACT_APP_TRAVEL_ADVISOR_KEY

const options = {
  method: 'GET',
  url: 'https://travel-advisor.p.rapidapi.com/restaurants/list-in-boundary',
  params: {
    bl_latitude: '11.847676',
    tr_latitude: '12.838442',
    bl_longitude: '109.095887',
    tr_longitude: '109.149359',
    restaurant_tagcategory_standalone: '10591',
    restaurant_tagcategory: '10591',
    limit: '30',
    currency: 'USD',
    open_now: 'false',
    lunit: 'km',
    lang: 'en_US'
  },
  headers: {
    'X-RapidAPI-Host': 'travel-advisor.p.rapidapi.com',
    'X-RapidAPI-Key': `${API_KEY}`,
  }
};

axios.request(options).then(function (response) {
	console.log(response.data);
}).catch(function (error) {
	console.error(error);
});

일단 지금 필요한 params는 위에서 4개뿐이다. 4개의 지점을 나타내는 좌표들을 사용하며, URL은 변수형태로 지정해서 나중에 함수를 작성할때 사용하기 수월하게 작성했다.

api/index.js

import axios from 'axios';

const URL = 'https://travel-advisor.p.rapidapi.com/restaurants/list-in-boundary'
const options = {
  method: 'GET',
  params: {
    bl_latitude: '11.847676',
    tr_latitude: '12.838442',
    bl_longitude: '109.095887',
    tr_longitude: '109.149359',

  },
  headers: {
    'X-RapidAPI-Host': 'travel-advisor.p.rapidapi.com',
    'X-RapidAPI-Key': '903e149d4emsh6409b1ca666eb6cp10efc3jsn9bf812cad056'
  }
};

export const getPlacesData = async () => {
    try{
       const {data: {data}} =await axios.get(URL, options);
       return data;
    } catch (error) {
        console.log(error)
    }
    
}

사용하는 api 매서드 url, 해당 메서드에서 사용되는 parmas, 그리고 header까지 가져온 뒤에 데이터를 가져오는 함수getPlaceData를 작성했다. 그다음 App.js에서 getPlaceData를 호출해보고 데이터가 불러와 지는지를 확인했다.

const [places, setPlaces] = useState([]);
  useEffect(() => {
    getPlacesData()
    .then((data)=> {
      console.log(data);
      setPlaces(data);
    })
  },[])

페이지가 로드 되면 getPlaceData를 호출해서 data를 콘솔창에 출력하게끔 작성했다.

데이터가 출력된 모습을 확인했다.
하나의 데이터에 매우 많은 양의 정보가 들어있어서 내가 원하는 정보만 가져오기위한 작업을 진행했다.

현재 나는 지도의 좌표를 고정값으로 지정했지만, 사용자가 지도를 드래그해도 해당 영역내에 존재하는 데이터를 보여줄수있도록 유동적으로 변환이 되어야 한다.

App.js에서 coordinate의 값과 bounds의 상태를 관리하는 usestate를 추가로 작성하고, Map컴포넌트로 props를 전달했다.

const [coordinates, setCoordinates] = useState({lat : 0, lng :0});
  const [bounds, setBounds] = useState(null);

return (
    <>
    <CssBaseline/>
    <Header/>
    <Grid container spacing={3} style={{width:'100%'}}>
        <Grid item xs={12} md={4}>
            <List />
        </Grid>
        <Grid item xs={12} md={8}>
            <Map
              setCoordinates = {setCoordinates}
              setBounds = {setBounds}
              coordinates = {coordinates}
            />
        </Grid>
    </Grid>

Map.js에서 고정값으로 지정한 coordinates를 제거한뒤 GoogleMapReact에서 변동이 생길 때마다 coordinates상태를 변경하는 코드를 작성하기 위해 onChange에 변동이 발생할 때마다 setCoordinates가 동작하며 경도와 위도를 console창에 출력하게 했다.


const Map = ({setCoordinates, setBounds, coordinates }) => {
    const classes = useStyles();
    const isMobile = useMediaQuery('(min-width:600px)');
 	 const API_KEY = process.env.REACT_APP_GOOGLE_MAPS_API_KEY;
  return (
        <div className={classes.mapContainer}>
            <GoogleMapReact
                bootstrapURLKeys={{key: API_KEY}}
                defaultCenter={coordinates}
                center={coordinates}
                defaultZoom={14}
                margin = {[50, 50, 50, 50]}
                options={''}
                onChange={(e) => {
                    console.log(e)
                    setCoordinates({lat: e.center.lat, lng: e.center.lng})
                }}
                onChildClick={''}
            >

            </GoogleMapReact>
        </div>  
    )
}

export default Map

지도의 위치가 바뀔 때마다 좌표정보가 콘솔에 출력되었다. 출력된 정보중에서도 필요한 내용은 boundscenter의 내부에 있는 lat lag값이다.

따라서 onChange가 발생할 때마다 coordinatesbound의 값이 변동되게 작성을 하고

onChange={(e) => {
                    setCoordinates({lat: e.center.lat, lng: e.center.lng})
                    setBounds({ne : e.marginBounds.ne, sw: e.marginBounds.sw})
                }}

App.js로 돌아가서 coordinate useState의 초기값을 변경한다음 페이지가 로드되고 사용자가 지도를 움직일 때마다 coordinatebounds의 값이 콘솔에 출력되도록 변경했다.
이 때 useEffect의 두번째 파라미터에 coordinatebounds 값을 넣어서 해당 값들이 변경될 때마다 리렌더링이 되게 해주어야 값이 출력될것이다.


  const [coordinates, setCoordinates] = useState({lat: 0, lng: 0});
  const [bounds, setBounds] = useState(null);

  useEffect(() => {
    console.log(coordinates,bounds);
    getPlacesData()
    .then((data)=> {
      console.log(data);
      setPlaces(data);
    })
  },[coordinates, bounds])

마우스 포인터로 지도를 드래그 한뒤 좌표가 출력되는 모습을 볼수 있다.
하지만, 처음에는 위도와 경도 모두 0(lat: 0, lng: 0)에서 시작되지 않고, 사용자의 위치에서 시작되게 할순 없을까?

geolocation Api를 사용하면 가능하다.
useEffect를 추가해서 사용자의 위치를 시작위치로 변경할 수 있다.

  const [coordinates, setCoordinates] = useState({});
  const [bounds, setBounds] = useState(null);

  useEffect(() => {
    navigator.geolocation.getCurrentPosition(({ coords: {latitude, longitude}}) => {
        setCoordinates({lat: latitude, lng: longitude});
    })
  },[])

  useEffect(() => {
    console.log(coordinates,bounds);
    getPlacesData()
    .then((data)=> {
      console.log(data);
      setPlaces(data);
    })
  },[coordinates, bounds])

geolocation api에서 좌표의 정보를 바로 객체로 받아온 다음에 setCoordinateslatlng값을 해당 좌표로 변경한다.

물론 초기값으로 지정한 lat:0, lng:0은 제거한다.
이렇게 실행하면 처음 화면이 사용자의 위치가 되지만, 크롬 개발자도구의 Devtools 맞춤 설정 및 제어 > 센서 에서 위치를 임의로 지정이 가능하다.

예를 들어서 위치를 mumbai로 설정하고 새로고침을 하면

이렇기 mumbai가 지도화면에 나타난다.

하지만 getPlaceData를 통해 받아온 data는 api에 지정한 pram내 작성된 좌표를 기준으로 표시되어있어서, 시작 화면만 사용자의 위치로 변했을 뿐 식당관련 정보는 변동되지 않았다.
따라서 api/index.js의 정보를 수정해야 내가 원하는 정보를 얻을 수 있다.

먼저 App.js에서 getPlaceData에서 bounds.sw 와 bounds.ne 값을 전달한다. 이 두가지의 값은 Map.js에서 setBounds로 인해 변경된 값을 나타낸다.

    getPlacesData(bounds.sw, bounds.ne)
    .then((data)=> {
      console.log(data);
      setPlaces(data);
    })

이제 api/index.js에서 고정값으로 사용하던 params의 값을 수정한다. 이때 swne를 받아와서 사용한다.

params: {
        bl_latitude: sw.lat,
        bl_longitude: sw.lng,
        tr_longitude: ne.lng,
        tr_latitude: ne.lat,
        
      },

이렇게 하면 현재 사용자의 위치가 표시된 지도의 영역에 맞는 식당들의 위치가 나타나게 된다.

※변경사항

bounds의 useState 초기값을 null 에서 {}로 변경했다.
api의 값들을 .env을 사용해서 변수로 변경했다.

이전에 list.jsplaces를 고정값으로 선언했지만, 이제 제거한 뒤 Travel_Adivor로부터 얻어온 값을 places로 대체하자. app.js의 List 컴포넌트에 placesprops를 전송한다.
app.js

<List  places={places}/>

현재 상황

이제 지도 화면의 이동에 따라 존재하는 식당의 목록이 왼쪽에 출력된다.

하지만 달랑 목록만 표시하기에는 Travel-Advisorapi가 보내주는 데이터의 종류가 많기 때문에 다양한 정보를 표시해주는 카드를 제작했다.

PlaceDetails

장소의 세부정보를 표시하는 컴포넌트를 작성한다.

  <Card elevation={6}>
      <CardMedia
          style={{height: 350}}
          image={place.photo ? place.photo.images.large.url : "http://sniblog.co.kr/wp-content/uploads/2018/08/20180819_202541.jpg"}
          title={place.name}
      />

      <CardContent>
        <Typography gutterBottom variant="h5">{place.name}</Typography>
      </CardContent>
    </Card>

Traver Advisor api에서 제공받는 정보 photo.images.large.url을 통해서 해당 식당에서 등록한 이미지를 가져오거나, 이미지가 없으면 임의로 선택한 이미지 링크를 추가하여 그 이미지를 대신 표시하게 했다. 이미지 아래에 식당의 이름을 출력하게 했다.

이제 식당의 이름만 나오는것이 아니라 해당 식당의 이미지가 표시된다.
이제 이미지 다음에 상세한 정보를 표시하게 했다.

먼저 식당의 이름이 나타나게 했다.

<Typography gutterBottom variant="h5">{place.name}</Typography>

그다음 가격의 정도를 표시해주는 문구를 작성한다. 이때 왼쪽에는 Price가 보이고 오른쪽에는 가격의 정도를 표시하는 $ 마크가 나타난다. $가 많을수록 비싼 식당임을 나타낸다.

<Box display="flex" justifyContent="space-between">
            <Typography variant ="subtitle1">Price</Typography>
            <Typography gutterBottom variant="subtitle1">{place.price_level}</Typography>
        </Box>

순위를 표시하는 항목도 작성했다. 지역에 있는 식당중 얼마나 상위권의 식당인지를 표시한다.

 <Box display="flex" justifyContent="space-between">
            <Typography variant ="subtitle1">Ranking</Typography>
            <Typography gutterBottom variant="subtitle1">{place.ranking}</Typography>
        </Box>

식당에서 수상한 경력이 있다면 수상한 내용을 표시하는것도 작성했다. Travel Advisorapi에서 place.awards가 존재한다면 나타내게 했다.

 {place?.awards?.map((award) => (
          <Box my={1} display="flex" justifyContent="space-between" alignItems="center">
              <img src={award.images.small} alt={award.display_name}/>
              <Typography variant="subtitle2" color="textSecondary">{award.display_name}</Typography>
          </Box>
        ))}

material-ui에서 가져온 chip컴포넌트를 사용해서 식당의 특성을 간단하게 표시하는 태그를 만들었다. 만약 place.cuisine의 데이터가 존재하는 경우에만 출력되도록 작성했다.

 {place?.cuisine?.map(({name}) => (
            <Chip key={name} size="small" label={name} className={classes.chip}/>
        ))}

그 다음에는 식당의 주소를 나타냈다. 왼쪽에는 주소아이콘을 표시하고 오른쪽에는 주소의 내용을 표시했다.

{place?.address && (
          <Typography gutterBottom variant="subtitle2" color="textSecondary" className={classes.subtitle}>
            <LocationOnIcon/> {place.address}
          </Typography>
        )}

식당의 전화번호도 표시했다.

   {place?.phone && (
          <Typography gutterBottom variant="subtitle2" color="textSecondary" className={classes.subtitle}>
            <PhoneIcon/> {place.phone}
          </Typography>
        )}

마지막으로 해당 식당의 정보를 가진 travel Advisor 사이트로 이동하는 버튼과, 식당이 자체적으로 제작한 사이트로 이동할수 있는 버튼을 제작했다.

 <CardActions>
          <Button size="small" color="primary" onClick={() => window.open(place.web_url, '_blank')}>
            Trip Advisor
          </Button>
          <Button size="small" color="primary" onClick={() => window.open(place.website, '_blank')}>
            Website
          </Button>
        </CardActions>

현재상황

이제 이미지뿐만 아니라 식당이름 및, 상세정보까지 나타났다.

지도에 식당정보 표시하기

이제 LIST에 식당의 상세정보가 나타난것 까지는 좋은데, 지도에도 해당 정보가 보이게 하자.

getPlaceData를 사용해서 받아온 places를 Map 컴포넌트에 props를 보낸다.
App.js

 <Grid item xs={12} md={8}>
            <Map
              setCoordinates = {setCoordinates}
              setBounds = {setBounds}
              coordinates = {coordinates}
              places={places}
            />
        </Grid>

Map.js에서 받아온 placesprops를사용해서 GoogleMapReact컴포넌트 안에 코드를 작성한다. Map의 파라미터에 places를 추가한뒤 작성하자.

 return (
   
        <div className={classes.mapContainer}>
            <GoogleMapReact
                bootstrapURLKeys={{key: API_KEY}}
                defaultCenter={coordinates}
                center={coordinates}
                defaultZoom={14}
                margin = {[50, 50, 50, 50]}
                options={''}
                onChange={(e) => {
                    setCoordinates({lat: e.center.lat, lng: e.center.lng})
                    setBounds({ne : e.marginBounds.ne, sw : e.marginBounds.sw})
                }}
                onChildClick={''}
            >
                {places?.map((place, i) => (
                    <div
                        className={classes.markerContainer}
                        lat={Number(place.latitude)}
                        lng={Number(place.longitude)}
                        key={i}
                    >
                        
                        {
                            !isDesktop ? (
                                <LocationOnOutlineIcon color="primary" fontSize="large"/>
                            ) : (
                                <Paper elevation={3} className={classes.paper}>
                                     <Typography className={classes.typography} variant="subtitle2" gutterBottom>
                                            {place.name}
                                     </Typography>
                                     <img
                                        className={classes.pointer}
                                        src = {place.photo ? place.photo.images.large.url : "http://sniblog.co.kr/wp-content/uploads/2018/08/20180819_202541.jpg"}
                                        alt = {place.name}
                                     >

                                     </img>
							<Rating size="small" value={Number(place.rating)} readOnly/>
                                </Paper>
                            )
                        }

                    </div>
                ))}
            </GoogleMapReact>
        </div>  
    )

이 이후로 429 오류가 발생했는데, rapidapi 의 계정이 basic이어서 request횟수가 초과되어 apikey 가 만료되어버렸다....

좋은 웹페이지 즐겨찾기