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...
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 getstated). 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 ai
cria-la 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!!!