# 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 &copy; <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 &copy; <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/)