# React / JavaScript Plugins
20161018 [<i class="fa fa-github"></i> Elantris](https://github.com/Elantris)
Note: https://hackmd.io/s/BJMW82v0
###### tags: `2016` `javascript` `handout`
## Setup
1. Get outdated packages.
```sh
$ brew outdated
$ npm outdated -g # npm outdated --global
```
2. Upgrade Node.js and NPM.
```sh
$ brew upgrade
$ npm i -g npm # npm install --global npm
```
> [Node.js Other Download](https://nodejs.org/en/download/current/)
> or
> [Node Version Manager](https://github.com/creationix/nvm)
3. Clone the build system.
```sh
$ git clone https://github.com/Elantris/react-boilerplate.git
$ cd react-boilerplate
$ npm install
```
Other Package Manager
* [Yarn](https://yarnpkg.com)
* [pnpm](https://github.com/rstacruz/pnpm)
Other Build System
* [joellongie / superCell](https://github.com/joellongie/superCell)
* [facebookincubator / create-react-app](https://github.com/facebookincubator/create-react-app)
[Create Apps with No Configuration | React](https://facebook.github.io/react/blog/2016/07/22/create-apps-with-no-configuration.html)
### Editor
* [Sublime Text](https://www.sublimetext.com)
* [GitHub Atom](https://atom.io)
* [Visual Studio Code](https://code.visualstudio.com)
### Package Control
The Sublime Text package manager.
[Official Site](https://packagecontrol.io)
* [Babel](https://packagecontrol.io/packages/Babel)
* [HTML-CSS-JS Prettify](https://packagecontrol.io/packages/HTML-CSS-PJS%20Prettify)
* [SCSS](https://packagecontrol.io/packages/SCSS)
## React
[Official Site](https://facebook.github.io/react/)
### Component
Let's write a markdown editor.
1. Load Dependencies.
```sh
npm i -S marked
```
```
// markdown-editor.jsx
var React = require('react');
var marked = require('marked');
```
2. Create React components in JSX.
```
// markdown-editor.jsx
var MarkdownEditor = React.createClass({
render: function() {
return (
<div>
<h1>Markdown Editor</h1>
<textarea />
<div />
</div>
);
}
});
module.exports = MarkdownEditor;
```
3. Mount to DOM.
```
// main.jsx
var React = require('react');
var ReactDOM = require('react-dom');
var MarkdownEditor = require('./markdown-editor.jsx');
ReactDOM.render(<MarkdownEditor />, document.getElementById('main'));
```
4. State, the internal variable in each component.
Use `getInitialState` to set the default state.
```
// markdown-editor.jsx
var MarkdownEditor = React.createClass({
getInitialState: function() {
return {
value: 'Type any text in the textarea.'
};
},
render: function() {
return (
<div>
<h1>Markdown Editor</h1>
<textarea defaultValue={this.state.value} />
<div />
</div>
);
}
});
```
5. Handle UI events.
Inputs, textareas have `onChange` property.
Use `handleChange` to define the callback function of event.
Use `this.setState` to change the state.
> [Event System | React](https://facebook.github.io/react/docs/events.html)
> [change - Event reference | MDN](https://developer.mozilla.org/en-US/docs/Web/Events/change)
```
// markdown-editor.jsx
var MarkdownEditor = React.createClass({
getInitialState: function() {
return {
value: 'Type any text in the textarea.'
};
},
handleChange: function(event) {
this.setState({
value: event.target.value
});
},
render: function() {
return (
<div>
<h1>Markdown Editor</h1>
<textarea defaultValue={this.state.value} onChange={this.handleChange} />
<div />
</div>
);
}
});
```
6. Set the innerHTML of an element.
Transform markdown text to html.
Add property `dangerouslySetInnerHTML` to an element.
```
// markdown-editor.jsx
var React = require('react');
var marked = require('marked');
var MarkdownEditor = React.createClass({
getInitialState: function() {
return {
value: 'Type any text in the textarea.'
};
},
handleChange: function(event) {
this.setState({
value: event.target.value
});
},
transformText: function() {
return {
__html: marked(this.state.value)
};
},
render: function() {
return (
<div>
<h1>Markdown Editor</h1>
<textarea defaultValue={this.state.value} onChange={this.handleChange} />
<div dangerouslySetInnerHTML={this.transformText()} />
</div>
);
}
});
module.exports = MarkdownEditor;
```
### Lifecycle
[Component Specs and Lifecycle | React](https://facebook.github.io/react/docs/component-specs.html)
* Specifications
- `render`
- `getInitialState`
- `getDefaultProps`
* Lifecycle
- `componentWillMount`
- `componentDidMount`
- `componentWillReceiveProps`
- `shouldComponentUpdate`
- `componentWillUpdate`
- `componentDidUpdate`
- `componentWillUnmount`
Initialize components
`getDefaultProps` -> `getInitialState` -> `componentWillMount` -> `render` -> `componentDidMount`
Update props or state
`componentWillReceiveProps` -> `shouldComponentUpdate` -> `componentWillUpdate` -> `render` -> `componentDidUpdate`
Unmounted
`componentWillUnmount`
![React.js lifecycle](http://imgh.us/react-lifecycle.svg)
> Source: http://imgh.us/react-lifecycle.svg
Let's build a map
1. Get data from [YouBike臺北市公共自行車即時資訊](http://data.taipei/youbike)
Unzip, rename to `YouBikeTP.json`, and move to js folder.
2. Install [Leaflet](http://leafletjs.com) by Bower
```sh
bower i -S leaflet
```
or by CDN
```html
<!-- index.html -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.0.1/dist/leaflet.js"></script>
```
3. Changes in `gulpfile.js`
```js
// gulpfile.js
gulp.task('bower', ['bower-install'], () => {
gulp.src([
'./bower_components/leaflet/dist/leaflet.css'
])
.pipe(cssmin())
.pipe(gulpif(env === 'development', sourcemaps.init()))
.pipe(concat('plugins.min.css'))
.pipe(gulpif(env === 'development', sourcemaps.write('./')))
.pipe(gulp.dest('./public/css/'))
gulp.src([
'./bower_components/leaflet/dist/leaflet.js'
])
.pipe(uglify())
.pipe(gulpif(env === 'development', sourcemaps.init()))
.pipe(concat('plugins.min.js'))
.pipe(gulpif(env === 'development', sourcemaps.write('./')))
.pipe(gulp.dest('./public/js/'))
})
```
```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="css/plugins.min.css">
<link rel="stylesheet" href="css/main.min.css">
</head>
<body>
<div id="main"></div>
<script src="js/plugins.min.js"></script>
<script src="js/main.lib.js"></script>
<script src="js/main.min.js"></script>
</body>
</html>
```
4. Write component in `youbike-station-map.jsx`
```
// youbike-station-map.jsx
var React = require('react');
var YouBikeStationMap = React.createClass({
render: function() {
return (
<div>
<div ref="container" style={{height: '400px'}} />
</div>
)
}
});
module.exports = YouBikeStationMap;
```
5. Mount to DOM.
```
// main.jsx
var React = require('react');
var ReactDOM = require('react-dom');
var MarkdownEditor = require('./markdown-editor.jsx');
var YouBikeStationMap = require('./youbike-station-map.jsx');
ReactDOM.render(<YouBikeStationMap />, document.getElementById('main'));
```
6. Build map after the component is mounted.
[Leaflet.js](http://leafletjs.com)
```
// youbike-station-map.jsx
var React = require('react');
var ACCESS_TOKEN = 'pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw';
var ATTR = 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' + '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' + 'Imagery © <a href="http://mapbox.com">Mapbox</a>';
var URL = 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=' + ACCESS_TOKEN;
var TP = require('./YouBikeTP.json');
var YouBikeStationMap = React.createClass({
map: null,
markers: {},
getInitialState: function() {
return {
filter: 5
};
},
componentDidMount: function() {
var mapbox_light = L.tileLayer(URL, {
attribution: ATTR,
id: 'mapbox.light'
});
this.map = L.map(this.refs.container, {
center: [25.0330, 121.5654],
zoom: 12,
layers: [mapbox_light]
});
},
render: function() {
return (
<div>
<div ref="container" style={{height: '400px'}} />
</div>
)
}
});
module.exports = YouBikeStationMap;
```
7. Draw markers on the map
```
// youbike-station-map.jsx
var YouBikeStationMap = React.createClass({
map: null,
markers: {},
getInitialState: function() {
return {
filter: 5
};
},
componentDidMount: function() {
var mapbox_light = L.tileLayer(URL, {
attribution: ATTR,
id: 'mapbox.light'
});
this.map = L.map(this.refs.container, {
center: [25.0330, 121.5654],
zoom: 12,
layers: [mapbox_light]
});
this.drawMarkers(this.state.filter);
},
drawMarkers: function(filter) {
for (var i in this.markers) {
this.markers[i].removeFrom(this.map);
}
for (var i in TP.retVal) {
if (~~TP.retVal[i].sbi >= filter) {
this.markers[i] = L.circle([TP.retVal[i].lat, TP.retVal[i].lng], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 10,
}).addTo(this.map);
}
}
},
render: function() {
return (
<div>
<div ref="container" style={{height: '400px'}} />
</div>
)
}
});
module.exports = YouBikeStationMap;
```
8. Add input field for filter and add event handler.
```
// youbike-station-map.jsx
var React = require('react');
var ACCESS_TOKEN = 'pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw';
var ATTR = 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' + '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' + 'Imagery © <a href="http://mapbox.com">Mapbox</a>';
var URL = 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=' + ACCESS_TOKEN;
var TP = require('./YouBikeTP.json');
var YouBikeStationMap = React.createClass({
map: null,
markers: {},
getInitialState: function() {
return {
filter: 5
};
},
componentDidMount: function() {
var mapbox_light = L.tileLayer(URL, {
attribution: ATTR,
id: 'mapbox.light'
});
this.map = L.map(this.refs.container, {
center: [25.0330, 121.5654],
zoom: 12,
layers: [mapbox_light]
});
this.drawMarkers(this.state.filter);
},
drawMarkers: function(filter) {
for (var i in this.markers) {
this.markers[i].removeFrom(this.map);
}
for (var i in TP.retVal) {
if (~~TP.retVal[i].sbi >= filter) {
this.markers[i] = L.circle([TP.retVal[i].lat, TP.retVal[i].lng], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 10,
}).addTo(this.map);
}
}
},
handleFilter: function() {
this.setState({
filter: this.refs.filter.value
});
},
componentWillUpdate: function(nextProps, nextState) {
this.drawMarkers(nextState.filter);
},
render: function() {
return (
<div>
<div ref="container" style={{height: '400px'}} />
<input ref="filter" type="number" defaultValue={this.state.filter} onChange={this.handleFilter} />
</div>
)
}
});
module.exports = YouBikeStationMap;
```
## Community
* [Mozilla Developer Network](https://developer.mozilla.org/)
Type `mdn.io/` to search keywords on MDN
* [stackoverflow](http://stackoverflow.com)
* [Front-End Developers Taiwan](https://www.facebook.com/groups/f2e.tw/)
* [ReactJS.tw](https://www.facebook.com/groups/reactjs.tw/)
* [node.js台灣](https://www.facebook.com/groups/node.js.tw/)