Criando uma aplicação de Mapas com React e Mapbox GL JS

Aplicação rolando =P.

Irei fazer uma série de ‘artigos’ mostrando como criar uma aplicação rica de funcionalidades utilizando React e Mapbox, também utilizaremos algumas bibliotecas interessantes como TurfJS e Mapbox GL Draw, sintam-se a vontade para opinar a respeito e dar pull requests, pois o intuito é sempre passar o conhecimento adiante, e quanto mais colaboradores melhor. Segue o bonde…

Documentações:

Código

O código encontra-se no meu Github.

Para rodar localmente:

git clone https://github.com/diihfilipe/mapster.git
npm i
npm start

Getting started

Primeiramente devemos instalar globalmente o Create React App usando npm install -g create-react-app.

Depois de instalado podemos navegar até uma pasta de preferencia e criar nossa primeira aplicação usando create-react-app nomedomeuprojeto o boilerplate básico vai ser intalado e configurado, e poderemos navegar até nomedomeuprojeto e dar um npm start pra vermos que a aplicação está no jeito para continuarmos o projeto (todo esse tutorial pode ser encontrado na documentação oficial do create-react-app).

Antes de começarmos vamos apagar os arquivos de hello-world do create-react-app e deixar apenas o necessário:

--/node_modules  
--/public
----index.html
----manifest.json
--/src
----App.css
----App.js
----index.css
----index.js
----registerServiceWorker.js
--.gitignore
--package.json
--README.md

Começaremos instalando o modulo prop-types pois chamar o metodo nativo está ‘deprecated’:

npm install --save prop-types

Agora vamos instalar os modulos mapbox-gl-js e material-ui:

npm install --save mapbox-gl
npm install --save material-ui

Para o desenvolvimento dos componentes em React nós vamos utilizar grande parte da Metodologia para desenvolvimento de projetos em React (web e native) feita pelo Rafael Correia. E para o design dos componentes utilizaremos o Material UI — “A Set of React Components that Implement Google’s Material Design”.

Como na propria documentação do material-ui nos diz, devemos instalar o modulo react-tap-event-plugin para ouvir eventos de touch, tap e click.

npm install --save react-tap-event-plugin

Logo em seguida importa-lo na raiz da aplicação, no caso o nosso index.js da /src:

import React from 'react';
import ReactDOM from 'react-dom'
import injectTapEventPlugin from 'react-tap-event-plugin'
import App from './App';
import registerServiceWorker from './registerServiceWorker'
import './index.css'
injectTapEventPlugin();
ReactDOM.render(<App />, document.getElementById('root'))
registerServiceWorker()

Vamos criar a pasta app/components dentro da /src para colocarmos nossos componentes que iremos criar:

mkdir src/app/components

Agora podemos criar nosso primeiro componente o header.js:

import React from 'react'
import PropTypes from 'prop-types'
import { AppBar } from 'material-ui'
const Header = ({title, iconClassNameRight}) => {
return(
<AppBar
title={title}
/>
)
}
const { string } = PropTypes
Header.propTypes = {
title: string.isRequired,
iconClassNameRight: string.isRequired
}
export default Header
// a principio nosso header vai receber apenas duas props

Vamos criar uma pasta chamada containers e criar um arquivo chamado homeContainer.js para seguirmos a ideia de componentização.

Importamos o header no nosso homeContainer.js:

import React, { Component } from 'react'
import Header from '../components/header'
class Home extends Component {
render(){
return(
<div>
<Header
title='Mapster'
iconClassNameRight='muidocs-icon-navigation-expand-more'
/>
</div>
)
}
}
export default Home

Agora vamos usar uma ‘trick’ bem massa, o material-ui tem todo o esquema de cores da UI Color Palette criado em forma de variáveis e isso nos permite dar uma customizada nas cores padrões sem ter que criar classes e CSS pŕoprios.

Vamos criar uma pasta chamada themes dentro da pasta app, e dentro dela criaremos um index.js:

// nosso index.js ficará assim
import { getMuiTheme } from 'material-ui/styles' // o metodo que lida com essa estilização
import { deepPurple600 } from 'material-ui/styles/colors' // nossa cor diferente da cor padrão
export const muiTheme = getMuiTheme({
appBar: {
color: deepPurple600
}
})
// note que exportamos uma constante que usa o metodo getMuiTheme num objeto o qual passamos
// o componente e as propriedades que queremos mudar.

Daí finalmente podemos ir no nosso App.js e deixa-lo dessa maneira:

import React, { Component } from 'react'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider' // esse cara lida com a customização
import { muiTheme } from './app/themes' // nosso metodo com estilo proprio
import './App.css';
import Home from './app/containers/homeContainer' // o container header que criamos
class App extends Component {
render() {
return (
<MuiThemeProvider muiTheme={muiTheme}>
<Home />
</MuiThemeProvider>
);
}
}
export default App;

Agora podemos sair dos ‘baby steps’ e evoluir nossa aplicação, criando nosso componente map.js assim:

irei explicar passo a passo…

//... acima tem os imports necessarios ...
class Map extends Component{ // criamos nosso componente Map como classe pois precisaremos usar algumas propriedades especificas
constructor(props){
super(props)
this.state = {
center: [-74.50, 40] // aqui setamos um estado inicial para ser o centro do mapa padrão
}
this.handleMap = this.handleMap.bind(this) // bind nas funções
this.handlePosition = this.handlePosition.bind(this)
this.handleFlyToAPosition = this.handleFlyToAPosition.bind(this)
}
  /*AGORA A GRANDE SACADA... é preciso criar uma função que lide com o mapa, que no caso é a handleMap, mas temos
um porém, se você não manja do lifecycle do react provavelmente voce iria quebrar a cabeça com isso, pois
temos que chamar a função que lida com o mapa APÓS o componente ser carregado, para que só assim ela reconheça
a div que setamos na propriedade 'container' caso contrario ele ira carregar o canvas para renderizar o mapa,
mas não irá encontrar o componente que precisa 'joga-lo' dentro, portanto sabendo disso, chamamos a função
handleMap na função componentDidMount, pois ela só vai ser executada depois do metodo render, ou seja, depois
que nossos componentes já tiverem sido carregados. */
  componentDidMount(){
const { container, style, zoom, accessToken } = this.props
const { center } = this.state
this.handleMap(container, style, center, zoom, accessToken)
this.handlePosition()
}
/* a função handleMap, vai receber as props que passamos no homeContainer, e vai criar uma constante chamada Map, e
que vai criar um novo mapa(a documentação do Mapbox tem todo esse getstarted). Depois passamos para o state essa
constante, apenas para não ficar criando objetos globais, pois precisaremos chamar esse cara depois.*/
  handleMap(container, style, center, zoom, accessToken){
mapboxgl.accessToken = accessToken
const map = new mapboxgl.Map({
container: container,
style: style,
center: center,
zoom: zoom
})
this.setState({
map: map
})
}
/*Aqui criamos uma função que usa uma feature do HTML5, chamada getCurrentPosition, que pega as informações de
latitude e longitude do browser, caso o usuario permita, daí jogamos essas informações no estado da nossa
aplicação para usar uma feature do mapbox logo em seguida, que vai fazer um 'flyTo' da posição inicial até a
posição do usuario, esse evento é chamado pelo clique do botão 'Me Encontre'.*/
  handlePosition(){
const options = {
enableHighAccuracy: true
}
navigator.geolocation.getCurrentPosition((pos) => {
const center = [pos.coords.longitude, pos.coords.latitude]
this.setState({
center: center
})
}, (err) => {
console.log(err)
}, options)
}
/*E finalmente a função que vai fazer esse 'flyTo', ela apenas pega as informações que jogamos no estado e executa
a ação, veja que aqui precisamos usar a constante 'map', pois quando voce usa o metodo 'new mapboxgl.Map'
somente essa constante possui os metodos para lidarmos com ações no mapa, por isso jogamos ela no estado para nao ficarmos gerando objetos globais.*/
  handleFlyToAPosition(){
const { center, map } = this.state
map.flyTo({
center: center
})
}
render(){
const { container, classNameStyle } = this.props
return(
<div id={container} className={classNameStyle}>
<div className='buttonMapTrackMe'> // uma classe própria para o posicionamento do componente botão
<Button
label='Me encontre'
primary={false}
onClickFunction={this.handleFlyToAPosition}
/>
</div>
      </div>
)
}
}
const { string, number } = PropTypes
Map.propTypes = {
container: string.isRequired,
style: string.isRequired,
classNameStyle: string.isRequired,
zoom: number.isRequired,
accessToken: string.isRequired
}
export default Map

Então temos nossa pequena aplicação, ainda sem muitas funcionalidades, porem utilizando React e Mapbox, também conseguimos criar nossos componentes com algumas boas práticas e focando no ‘modelo’ de desenvolvimento utilizando React. Nos proximos artigos irei focar mais em como criar features interessantes usando das ferramentas do Mapbox e do conceito de desenvolvimento com React, portanto teremos menos ‘step-by-step’ de ‘como escrever código’ e mais discussões a respeito das ferramentas e features. Até a proxima…

feel free to pull request me if you want!!!