├── 9781484212615.jpg ├── LICENSE.txt ├── README.md ├── chapter 1 └── groceryList │ ├── LICENSE │ ├── README.md │ ├── app │ └── App.js │ ├── package.json │ ├── public │ ├── index.html │ └── styles.css │ └── webpack.config.js ├── chapter 2 └── JSX │ ├── LICENSE │ ├── README.md │ ├── app │ ├── App.js │ ├── BlankSpace.js │ ├── CommentForm.js │ ├── Comments.js │ └── Conditional.js │ ├── package.json │ ├── public │ ├── index.html │ └── styles.css │ └── webpack.config.js ├── chapter 3 ├── contactsApp │ ├── LICENSE │ ├── README.md │ ├── app │ │ └── App.js │ ├── package.json │ ├── public │ │ ├── contacts.json │ │ ├── index.html │ │ └── styles.css │ └── webpack.config.js └── proptype & defaultprop │ ├── LICENSE │ ├── README.md │ ├── app │ └── App.js │ ├── package.json │ ├── public │ ├── index.html │ └── styles.css │ └── webpack.config.js ├── chapter 4 ├── animation │ ├── app │ │ └── App.js │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── main.css │ └── webpack.config.js └── dragndrop │ ├── LICENSE │ ├── README.md │ ├── app │ ├── App.js │ ├── Container.js │ ├── ShoppingCart.js │ ├── Snack.js │ └── constants.js │ ├── package.json │ ├── public │ ├── index.html │ └── styles.css │ └── webpack.config.js ├── chapter 5 └── router │ ├── LICENSE │ ├── README.md │ ├── app │ ├── About.js │ ├── App.js │ ├── Home.js │ ├── RepoDetails.js │ ├── Repos.js │ └── ServerError.js │ ├── package.json │ ├── public │ ├── index.html │ └── styles.css │ └── webpack.config.js ├── chapter 6 ├── aircheap │ ├── LICENSE │ ├── README.md │ ├── app │ │ ├── App.js │ │ ├── AppDispatcher.js │ │ ├── actions │ │ │ └── AirportActionCreators.js │ │ ├── api │ │ │ └── AirCheapAPI.js │ │ ├── components │ │ │ └── TicketItem.js │ │ ├── constants.js │ │ └── stores │ │ │ ├── AirportStore.js │ │ │ ├── RouteStore.js │ │ │ └── TicketStore.js │ ├── package.json │ ├── public │ │ ├── airports.json │ │ ├── flights.json │ │ ├── index.html │ │ ├── logo.png │ │ └── styles.css │ └── webpack.config.js └── fluxbank │ ├── LICENSE │ ├── README.md │ ├── app │ ├── App.js │ ├── AppDispatcher.js │ ├── BankActions.js │ ├── BankBalanceStore.js │ ├── BankRewardsStore.js │ └── constants.js │ ├── package.json │ ├── public │ ├── index.html │ └── styles.css │ └── webpack.config.js ├── chapter 7 └── clock │ ├── LICENSE │ ├── README.md │ ├── app │ ├── App.js │ ├── Clock.js │ └── Digit.js │ ├── dist │ └── bundle.js │ ├── package.json │ ├── public │ ├── index.html │ └── main.css │ └── webpack.config.js ├── chapter 8 ├── helloexpress │ ├── .babelrc │ ├── package.json │ ├── server.js │ └── views │ │ └── index.ejs └── universal-react │ ├── .babelrc │ ├── app │ ├── components │ │ ├── App.js │ │ ├── ContactItem.js │ │ ├── ContactList.js │ │ ├── ContactsApp.js │ │ ├── Home.js │ │ └── SearchBar.js │ └── routes.js │ ├── browser.js │ ├── index.ejs │ ├── package.json │ ├── public │ └── contacts.json │ ├── server.js │ └── webpack.config.js ├── chapter 9 └── checkboxWithLabel │ ├── CheckboxWithLabel.js │ ├── __tests__ │ └── CheckboxWithLabel_shallow_test.js │ └── package.json ├── contributing.md └── kanban-app ├── .gitignore ├── LICENSE ├── README.md ├── app ├── App.js ├── Card.js ├── CheckList.js ├── KanbanBoard.js └── List.js ├── package.json ├── public ├── index.html └── styles.css └── webpack.config.js /9781484212615.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/pro-react/a1d7f723a2acbb365f983f601d906152a1e08723/9781484212615.jpg -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/pro-react/a1d7f723a2acbb365f983f601d906152a1e08723/LICENSE.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*Pro React*](http://www.apress.com/9781484212615) by Cassio de Sousa Antonio (Apress, 2015). 4 | 5 | ![Cover image](9781484212615.jpg) 6 | 7 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 8 | 9 | ## Releases 10 | 11 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 12 | 13 | ## Contributions 14 | 15 | See the file Contributing.md for more information on how you can contribute to this repository. 16 | -------------------------------------------------------------------------------- /chapter 1/groceryList/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 1/groceryList/README.md: -------------------------------------------------------------------------------- 1 | Grocery List 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 1/groceryList/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; import {render} from 'react-dom'; // Parent Component class GroceryList extends Component { render() { return ( ); } } // Child Component class ListItem extends Component { render() { return (
  • {this.props.quantity}× {this.props.children}
  • ); } } render(, document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 1/groceryList/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "react": "^15.0.0", 21 | "react-dom": "^15.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter 1/groceryList/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 1/groceryList/public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 16px/1 sans-serif; 3 | } 4 | #root { 5 | height: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /chapter 1/groceryList/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 2/JSX/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 2/JSX/README.md: -------------------------------------------------------------------------------- 1 | JSX Quirks 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 2/JSX/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; import {render} from 'react-dom'; import Conditional from './Conditional'; import BlankSpace from './BlankSpace'; import Comments from './Comments'; import CommentForm from './CommentForm'; // Parent Component class App extends Component { render() { return (

    JSX Quirks

    Conditionals

    Blank Space

    Comments in JSX

    React Without JSX

    ); } } render(, document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 2/JSX/app/BlankSpace.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | // Parent Component 4 | class BlankSpace extends Component { 5 | render() { 6 | return( 7 |
    8 | Google{" "} 9 | Facebook 10 |
    11 | ) 12 | } 13 | } 14 | 15 | export default BlankSpace 16 | -------------------------------------------------------------------------------- /chapter 2/JSX/app/CommentForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | let { 4 | form, 5 | input 6 | } = React.DOM; 7 | 8 | class CommentForm extends Component { 9 | render(){ 10 | return form({className:"commentForm"}, 11 | input({type:"text", placeholder:"Name"}), 12 | input({type:"text", placeholder:"Comment"}), 13 | input({type:"submit", value:"Post"}) 14 | ) 15 | } 16 | } 17 | 18 | export default CommentForm 19 | -------------------------------------------------------------------------------- /chapter 2/JSX/app/Comments.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | // Parent Component 4 | class Comments extends Component { 5 | render() { 6 | return( 7 |
    8 | {/* put {} around comments */} 9 | This component has comments 10 |
    11 | ) 12 | } 13 | } 14 | 15 | export default Comments 16 | -------------------------------------------------------------------------------- /chapter 2/JSX/app/Conditional.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | // Parent Component 4 | class Conditional extends Component { 5 | render() { 6 | let condition = true; 7 | return ( 8 |
    9 | {condition ? 10 | Hello JSX (The condition was true) 11 | : null} 12 |
    13 | ); 14 | } 15 | } 16 | 17 | export default Conditional 18 | -------------------------------------------------------------------------------- /chapter 2/JSX/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "react": "^15.0.0", 21 | "react-dom": "^15.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter 2/JSX/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 2/JSX/public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 16px/1 sans-serif; 3 | } 4 | #root { 5 | height: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /chapter 2/JSX/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 3/contactsApp/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 3/contactsApp/README.md: -------------------------------------------------------------------------------- 1 | Grocery List 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 3/contactsApp/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; import { render } from 'react-dom'; import 'whatwg-fetch'; class ContactsAppContainer extends Component { constructor(){ super(); this.state={ contacts: [] }; } componentDidMount(){ fetch('./contacts.json') .then((response) => response.json()) .then((responseData) => { this.setState({contacts: responseData}); }) .catch((error) => { console.log('Error fetching and parsing data', error); }); } render(){ return ( ); } } // Renders a SearchBar and a ContactList // Passes down filterText state and handleUserInput callback as props class ContactsApp extends Component { constructor(){ super(); this.state={ filterText: '' }; } handleUserInput(searchTerm){ this.setState({filterText:searchTerm}) } render(){ return(
    ) } } ContactsApp.propTypes = { contacts: PropTypes.arrayOf(PropTypes.object) } // Pure component that receives 2 props from the parent // filterText (string) and onUserInput (callback function) class SearchBar extends Component { handleChange(event){ this.props.onUserInput(event.target.value) } render(){ return } } SearchBar.propTypes = { onUserInput: PropTypes.func.isRequired, filterText: PropTypes.string.isRequired } // Pure component that receives both contacts and filterText as props // The component is responsible for actualy filtering the // contacts before displaying them. // It's considered a pure component because given the same // contacts and filterText props the output will always be the same. class ContactList extends Component { render(){ let filteredContacts = this.props.contacts.filter( (contact) => contact.name.indexOf(this.props.filterText) !== -1 ); return(
      {filteredContacts.map( (contact) => )}
    ) } } ContactList.propTypes = { contacts: PropTypes.arrayOf(PropTypes.object), filterText: PropTypes.string.isRequired } class ContactItem extends Component { render() { return
  • {this.props.name} - {this.props.email}
  • } } ContactItem.propTypes = { name: PropTypes.string.isRequired, email: PropTypes.string.isRequired } render(, document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 3/contactsApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "react": "^15.0.0", 21 | "react-dom": "^15.0.0", 22 | "whatwg-fetch": "^0.11.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter 3/contactsApp/public/contacts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Cassio Zen", 4 | "email": "cassiozen@gmail.com" 5 | }, 6 | { 7 | "name": "Dan Abramov", 8 | "email": "gaearon@somewhere.com" 9 | }, 10 | { 11 | "name": "Pete Hunt", 12 | "email": "floydophone@somewhere.com" 13 | }, 14 | { 15 | "name": "Paul O’Shannessy", 16 | "email": "zpao@somewhere.com" 17 | }, 18 | { 19 | "name": "Ryan Florence", 20 | "email": "rpflorence@somewhere.com" 21 | }, 22 | { 23 | "name": "Sebastian Markbage", 24 | "email": "sebmarkbage@here.com" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /chapter 3/contactsApp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 3/contactsApp/public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 16px/1 sans-serif; 3 | } 4 | #root { 5 | height: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /chapter 3/contactsApp/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 3/proptype & defaultprop/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 3/proptype & defaultprop/README.md: -------------------------------------------------------------------------------- 1 | Proptypes and Defaul Props 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 3/proptype & defaultprop/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; import { render } from 'react-dom'; class Greeter extends Component { render() { return (

    {this.props.salutation}

    ) } } Greeter.propTypes = { salutation: PropTypes.string.isRequired } Greeter.defaultProps = { salutation: "Hello World" } render(, document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 3/proptype & defaultprop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "react": "^15.0.0", 21 | "react-dom": "^15.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter 3/proptype & defaultprop/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 3/proptype & defaultprop/public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 16px/1 sans-serif; 3 | } 4 | #root { 5 | height: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /chapter 3/proptype & defaultprop/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 4/animation/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; import { render } from 'react-dom'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; class AnimatedShoppingList extends Component { constructor(){ super(...arguments); // Create an "items" state pre-populated with some shopping items this.state={ items: [ {id:1, name: 'Milk'}, {id:2, name: 'Yogurt'}, {id:3, name: 'Orange Juice'}, ] } } // Called when the user changes the input field handleChange(evt) { if(evt.key === 'Enter'){ // Create a new item and set the current time as it's id let newItem = {id:Date.now(), name:evt.target.value} // Create a new array with the previous items plus the value the user typed let newItems = this.state.items.concat(newItem); // Clear the text field evt.target.value=''; // Set the new state this.setState({items: newItems}); } } // Called when the user Clicks on a shopping item handleRemove(i) { // Create a new array without the clicked item var newItems = this.state.items; newItems.splice(i, 1); // Set the new state this.setState({items: newItems}); } render(){ let shoppingItems = this.state.items.map((item, i) => (
    {item.name}
    )); return(
    {shoppingItems}
    ); } }; render(, document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 4/animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "react": "^15.0.0", 21 | "react-addons-css-transition-group": "^15.0.0", 22 | "react-addons-transition-group": "^15.0.0", 23 | "react-dom": "^15.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter 4/animation/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 4/animation/public/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica; 3 | } 4 | 5 | input { 6 | padding: 5px; 7 | width: 120px; 8 | margin-top:10px; 9 | } 10 | 11 | .item { 12 | background-color: #efefef; 13 | cursor: pointer; 14 | display: block; 15 | margin-bottom: 1px; 16 | padding: 8px 12px; 17 | width: 120px; 18 | } 19 | 20 | 21 | .example-enter { 22 | opacity: 0; 23 | transform: translateX(-250px); 24 | } 25 | .example-enter.example-enter-active { 26 | opacity: 1; 27 | transform: translateX(0); 28 | transition: 0.3s; 29 | } 30 | 31 | 32 | .example-leave { 33 | opacity: 1; 34 | transform: translateX(0); 35 | } 36 | 37 | .example-leave.example-leave-active { 38 | opacity: 0; 39 | transform: translateX(250px); 40 | transition: 0.3s; 41 | } 42 | 43 | .example-appear { 44 | opacity: 0; 45 | transform: translateX(-250px); 46 | } 47 | .example-appear.example-appear-active { 48 | opacity: 1; 49 | transform: translateX(0); 50 | transition: .3s; 51 | } 52 | -------------------------------------------------------------------------------- /chapter 4/animation/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: [ 6 | './app/App.js' 7 | ], 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: 'bundle.js' 11 | }, 12 | resolve: { 13 | extensions: ['', '.js', '.jsx'] 14 | }, 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.jsx?$/, 19 | exclude: /node_modules/, 20 | loader: 'babel', 21 | query: { 22 | presets: ['es2015','react'] 23 | } 24 | } 25 | ] 26 | }, 27 | plugins: [ 28 | new webpack.HotModuleReplacementPlugin(), 29 | new webpack.NoErrorsPlugin() 30 | ], 31 | devtool: 'eval-source-map', 32 | devServer: { 33 | contentBase: "./public", 34 | colors: true, 35 | historyApiFallback: true, 36 | hot: true, 37 | inline: true, 38 | progress: false, 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/README.md: -------------------------------------------------------------------------------- 1 | Drag and Drop 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; import {render} from 'react-dom'; import Container from './Container' class App extends Component { render(){ return ( ); } } render(, document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 4/dragndrop/app/Container.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ShoppingCart from './ShoppingCart'; 3 | import Snack from './Snack'; 4 | import { DragDropContext } from 'react-dnd'; 5 | import HTML5Backend from 'react-dnd-html5-backend'; 6 | 7 | class Container extends Component { 8 | render() { 9 | return ( 10 |
    11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 | ); 19 | } 20 | } 21 | 22 | export default DragDropContext(HTML5Backend)(Container); 23 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/app/ShoppingCart.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import { DropTarget } from 'react-dnd'; 3 | import constants from './constants'; 4 | 5 | // ShoppingCart DND Spec 6 | // "A plain object implementing the drop target specification" 7 | // 8 | // - DropTarget Methods (All optional) 9 | // - drop: Called when a compatible item is dropped. 10 | // - hover: Called when an item is hovered over the component. 11 | // - canDrop: Use it to specify whether the drop target is able to accept 12 | // the item. 13 | const ShoppingCartSpec = { 14 | drop() { 15 | return { name: 'ShoppingCart' }; 16 | } 17 | }; 18 | 19 | // ShoppingCart DropTarget - collect 20 | // "The collecting function. 21 | // 22 | // - connect: An instance of DropTargetConnector. 23 | // You use it to assign the drop target role to a DOM node. 24 | // 25 | // - monitor: An instance of DropTargetMonitor. 26 | // You use it to connect state from the React DnD to props. 27 | // Available functions to get state include canDrop(), isOver() and didDrop() 28 | let collect = (connect, monitor) => { 29 | return { 30 | connectDropTarget: connect.dropTarget(), 31 | isOver: monitor.isOver(), 32 | canDrop: monitor.canDrop() 33 | }; 34 | } 35 | 36 | class ShoppingCart extends Component { 37 | render() { 38 | const { canDrop, isOver, connectDropTarget } = this.props; 39 | const isActive = canDrop && isOver; 40 | 41 | let backgroundColor = '#FFFFFF'; 42 | if (isActive) { 43 | backgroundColor = '#F7F7BD'; 44 | } else if (canDrop) { 45 | backgroundColor = '#F7F7F7'; 46 | } 47 | 48 | const style = { 49 | backgroundColor: backgroundColor 50 | }; 51 | 52 | return connectDropTarget( 53 |
    54 | {isActive ? 55 | 'Hummmm, snack!' : 56 | 'Drag here to order!' 57 | } 58 |
    59 | ); 60 | } 61 | } 62 | 63 | ShoppingCart.propTypes = { 64 | connectDropTarget: PropTypes.func.isRequired, 65 | isOver: PropTypes.bool.isRequired, 66 | canDrop: PropTypes.bool.isRequired 67 | } 68 | 69 | export default DropTarget(constants.SNACK, ShoppingCartSpec, collect)(ShoppingCart); 70 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/app/Snack.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { DragSource } from 'react-dnd'; 3 | import constants from './constants'; 4 | 5 | 6 | // snack Drag'nDrop spec 7 | // 8 | // - Required: beginDrag 9 | // - Optional: endDrag 10 | // - Optional: canDrag 11 | // - Optional: isDragging 12 | const snackSpec = { 13 | beginDrag(props) { 14 | return { 15 | name: props.name 16 | }; 17 | }, 18 | 19 | endDrag(props, monitor) { 20 | const dragItem = monitor.getItem(); 21 | const dropResult = monitor.getDropResult(); 22 | 23 | if (dropResult) { 24 | console.log(`You dropped ${dragItem.name} into ${dropResult.name}`); 25 | } 26 | } 27 | }; 28 | 29 | // Snack DragSource collect collecting function. 30 | // - connect: An instance of DragSourceConnector. 31 | // You use it to assign the drag source role to a DOM node. 32 | // 33 | // - monitor: An instance of DragSourceMonitor. 34 | // You use it to connect state from the React DnD to your component’s properties. 35 | // Available functions to get state include canDrag(), isDragging(), getItemType(), 36 | // getItem(), didDrop() etc. 37 | let collect = (connect, monitor) => { 38 | return { 39 | connectDragSource: connect.dragSource(), 40 | isDragging: monitor.isDragging() 41 | }; 42 | } 43 | 44 | class Snack extends Component { 45 | render() { 46 | const { name, isDragging, connectDragSource } = this.props; 47 | const opacity = isDragging ? 0.4 : 1; 48 | 49 | const style = { 50 | opacity: opacity 51 | }; 52 | 53 | return ( 54 | connectDragSource( 55 |
    56 | {name} 57 |
    58 | ) 59 | ); 60 | } 61 | } 62 | 63 | Snack.propTypes = { 64 | connectDragSource: PropTypes.func.isRequired, 65 | isDragging: PropTypes.bool.isRequired, 66 | name: PropTypes.string.isRequired 67 | }; 68 | 69 | export default DragSource(constants.SNACK, snackSpec, collect)(Snack); 70 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/app/constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | SNACK: 'snack' 3 | }; 4 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "react": "^15.0.0", 21 | "react-dnd": "^2.1.4", 22 | "react-dnd-html5-backend": "^2.1.2", 23 | "react-dom": "^15.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 16px/1 sans-serif; 3 | } 4 | #root { 5 | height: 100%; 6 | } 7 | h1 { 8 | font-weight: 200; 9 | color: #3b414c; 10 | font-size: 20px; 11 | } 12 | .app { 13 | white-space: nowrap; 14 | height: 100%; 15 | } 16 | 17 | .snack { 18 | display: inline-block; 19 | padding: .5em; 20 | margin: 0 1em 1em 0.25em; 21 | border: 4px solid #d9d9d9; 22 | background: #f7f7f7; 23 | height: 5rem; 24 | width: 5rem; 25 | border-radius: 5rem; 26 | cursor: pointer; 27 | line-height: 5em; 28 | text-align: center; 29 | color: #333; 30 | } 31 | .shopping-cart { 32 | border: 5px dashed #d9d9d9; 33 | border-radius: 10px; 34 | padding: 5rem 2rem; 35 | text-align: center; 36 | } 37 | -------------------------------------------------------------------------------- /chapter 4/dragndrop/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 5/router/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 5/router/README.md: -------------------------------------------------------------------------------- 1 | React Router sample code 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 5/router/app/About.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class About extends Component { 4 | render() { 5 | return ( 6 |

    {this.props.route.title}

    7 | ); 8 | } 9 | } 10 | 11 | export default About; 12 | -------------------------------------------------------------------------------- /chapter 5/router/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; import { render } from 'react-dom'; import { Router, Route, IndexRoute, Link } from 'react-router'; import createBrowserHistory from 'history/lib/createBrowserHistory' import About from './About'; import Repos from './Repos'; import RepoDetails from './RepoDetails'; import Home from './Home'; import ServerError from './ServerError'; class App extends Component { render() { return (
    App
    • About
    • Repos
    {this.props.children}
    ); } } render(( ), document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 5/router/app/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Home extends Component { 4 | render() { 5 | return ( 6 |

    HOME

    7 | ); 8 | } 9 | } 10 | 11 | export default Home; 12 | -------------------------------------------------------------------------------- /chapter 5/router/app/RepoDetails.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import 'babel-polyfill'; 3 | 4 | class RepoDetails extends Component { 5 | 6 | renderRepository() { 7 | let repository = this.props.repositories.find((repo)=>repo.name === this.props.params.repo_name); 8 | let stars = []; 9 | for (var i = 0; i < repository.stargazers_count; i++) { 10 | stars.push('★'); 11 | } 12 | return( 13 |
    14 |

    {repository.name}

    15 |

    {repository.description}

    16 | {stars} 17 |
    18 | ); 19 | } 20 | 21 | render() { 22 | if(this.props.repositories.length > 0 ){ 23 | return this.renderRepository(); 24 | } else { 25 | return

    Loading...

    ; 26 | } 27 | } 28 | } 29 | 30 | export default RepoDetails; 31 | -------------------------------------------------------------------------------- /chapter 5/router/app/Repos.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import 'whatwg-fetch'; 4 | 5 | class Repos extends Component { 6 | constructor(){ 7 | super(...arguments); 8 | this.state = { 9 | repositories: [] 10 | }; 11 | } 12 | 13 | componentDidMount(){ 14 | fetch('https://api.github.com/users/pro-react/repos') 15 | .then((response) => { 16 | if(response.ok){ 17 | return response.json(); 18 | } else { 19 | throw new Error("Server response wasn't OK"); 20 | } 21 | }) 22 | .then((responseData) => { 23 | this.setState({repositories:responseData}); 24 | }) 25 | .catch((error) => { 26 | this.props.history.pushState(null,'/error'); 27 | }); 28 | } 29 | 30 | render() { 31 | let repos = this.state.repositories.map((repo) => ( 32 |
  • {repo.name}
  • 33 | )); 34 | 35 | let child = this.props.children && React.cloneElement(this.props.children, 36 | { repositories: this.state.repositories } 37 | ); 38 | 39 | return ( 40 |
    41 |

    Github Repos

    42 |
      43 | {repos} 44 |
    45 | {child} 46 |
    47 | ); 48 | } 49 | } 50 | 51 | export default Repos; 52 | -------------------------------------------------------------------------------- /chapter 5/router/app/ServerError.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | const styles={ 4 | root:{ 5 | textAlign:'center' 6 | }, 7 | alert:{ 8 | fontSize:80, 9 | fontWeight: 'bold', 10 | color:'#e9ab2d' 11 | } 12 | }; 13 | 14 | class ServerError extends Component { 15 | render() { 16 | return ( 17 |
    18 |
    {/* ⚠ is the html entity code for the warning character: ⚠ */} 19 |

    Ops, we have a problem

    20 |

    Sorry, we could't access the repositories. Please try again in a few moments.

    21 |
    22 | ); 23 | } 24 | } 25 | 26 | export default ServerError; 27 | -------------------------------------------------------------------------------- /chapter 5/router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "babel-polyfill": "^6.7.4", 21 | "history": "^1.13.0", 22 | "react": "^15.0.0", 23 | "react-dom": "^15.0.0", 24 | "react-router": "^1.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter 5/router/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 5/router/public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font: 16px/1 sans-serif; 4 | } 5 | menu ul{ 6 | margin: 0; 7 | padding: 0; 8 | } 9 | menu li { 10 | display: inline-block; 11 | padding: 5px; 12 | } 13 | a.active { 14 | color: #444; 15 | font-weight: bold; 16 | text-decoration: none; 17 | } 18 | header { 19 | padding: 10px; 20 | background-color: #333; 21 | color: #ccc; 22 | font-size: 20px; 23 | font-weight: bold; 24 | } 25 | menu { 26 | background-color: #ccc; 27 | padding: 5px; 28 | margin-top: 0; 29 | margin-bottom: 10px; 30 | } 31 | -------------------------------------------------------------------------------- /chapter 5/router/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 6/aircheap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 6/aircheap/README.md: -------------------------------------------------------------------------------- 1 | AirCheap 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 6/aircheap/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; import { render } from 'react-dom'; import ReactDOM from 'react-dom'; import {Container} from 'flux/utils'; import Autosuggest from 'react-autosuggest'; import AirportStore from './stores/AirportStore'; import RouteStore from './stores/RouteStore'; import TicketStore from './stores/TicketStore'; import TicketItem from './components/TicketItem'; import AirportActionCreators from './actions/AirportActionCreators'; class App extends Component { getSuggestions(input, callback) { const escapedInput = input.trim().toLowerCase(); const airportMatchRegex = new RegExp('\\b' + escapedInput, 'i'); const suggestions = this.state.airports .filter(airport => airportMatchRegex.test(airport.city)) .sort((airport1, airport2) => { return airport1.city.toLowerCase().indexOf(escapedInput) - airport2.city.toLowerCase().indexOf(escapedInput) }) .slice(0, 7) .map(airport => `${airport.city} - ${airport.country} (${airport.code})`); callback(null, suggestions); } handleSelect(target, suggestion, event){ const airportCodeRegex = /\(([^)]+)\)/; let airportCode = airportCodeRegex.exec(suggestion)[1]; AirportActionCreators.chooseAirport(target, airportCode); } componentDidMount(){ AirportActionCreators.fetchAirports(); } componentWillUpdate(nextProps, nextState){ let originAndDestinationSelected = nextState.origin && nextState.destination; let selectionHasChangedSinceLastUpdate = nextState.origin !== this.state.origin || nextState.destination !== this.state.destination; if(originAndDestinationSelected && selectionHasChangedSinceLastUpdate){ AirportActionCreators.fetchTickets(nextState.origin, nextState.destination); } } render() { let ticketList = this.state.tickets.map((ticket)=>( )); return (

    Check discount ticket prices and pay using your AirCheap points

    {ticketList}
    ); } } App.getStores = () => ([AirportStore, RouteStore,TicketStore]); App.calculateState = (prevState) => ({ airports: AirportStore.getState(), origin: RouteStore.get('origin'), destination: RouteStore.get('destination'), tickets: TicketStore.getState() }); const AppContainer = Container.create(App); render(, document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 6/aircheap/app/AppDispatcher.js: -------------------------------------------------------------------------------- 1 | import {Dispatcher} from 'flux'; 2 | import 'babel-polyfill'; 3 | 4 | 5 | class AppDispatcher extends Dispatcher{ 6 | dispatch(action = {}) { 7 | console.log("Dispatched", action.type); 8 | super.dispatch(action); 9 | } 10 | 11 | /** 12 | * Dispatches three actions for an async operation represented by promise. 13 | */ 14 | dispatchAsync(promise, types, payload){ 15 | const { request, success, failure } = types; 16 | this.dispatch({ type: request, payload: Object.assign({}, payload) }); 17 | promise.then( 18 | (response) => { 19 | this.dispatch({ 20 | type: success, 21 | payload: Object.assign({}, payload, { response }) 22 | }) 23 | }, 24 | error => this.dispatch({ 25 | type: failure, 26 | payload: Object.assign({}, payload, { error }) 27 | }) 28 | ); 29 | } 30 | } 31 | 32 | export default new AppDispatcher(); 33 | -------------------------------------------------------------------------------- /chapter 6/aircheap/app/actions/AirportActionCreators.js: -------------------------------------------------------------------------------- 1 | import AppDispatcher from '../AppDispatcher'; 2 | import constants from '../constants' 3 | import AirCheapAPI from '../api/AirCheapAPI'; 4 | 5 | let AirportActionCreators = { 6 | 7 | fetchAirports() { 8 | AppDispatcher.dispatchAsync(AirCheapAPI.fetchAirports(), { 9 | request: constants.FETCH_AIRPORTS, 10 | success: constants.FETCH_AIRPORTS_SUCCESS, 11 | failure: constants.FETCH_AIRPORTS_ERROR 12 | }); 13 | }, 14 | 15 | chooseAirport(target, code) { 16 | AppDispatcher.dispatch({ 17 | type: constants.CHOOSE_AIRPORT, 18 | target: target, 19 | code: code 20 | }); 21 | }, 22 | 23 | fetchTickets(origin, destination) { 24 | AppDispatcher.dispatchAsync(AirCheapAPI.fetchTickets(origin, destination), { 25 | request: constants.FETCH_TICKETS, 26 | success: constants.FETCH_TICKETS_SUCCESS, 27 | failure: constants.FETCH_TICKETS_ERROR 28 | }); 29 | } 30 | 31 | }; 32 | 33 | export default AirportActionCreators; 34 | -------------------------------------------------------------------------------- /chapter 6/aircheap/app/api/AirCheapAPI.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch'; 2 | 3 | let AirCheapAPI = { 4 | fetchAirports() { 5 | return fetch('airports.json') 6 | .then((response) => response.json()); 7 | }, 8 | 9 | fetchTickets(origin, destination) { 10 | return fetch('flights.json') 11 | .then((response) => response.json()); 12 | } 13 | }; 14 | 15 | export default AirCheapAPI; 16 | -------------------------------------------------------------------------------- /chapter 6/aircheap/app/components/TicketItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | // Default data configuration 4 | const dateConfig = { 5 | weekday: "short", 6 | year: "numeric", 7 | month: "short", 8 | day: "numeric", 9 | hour: "2-digit", 10 | minute: "2-digit" 11 | }; 12 | 13 | class TicketItem extends Component { 14 | render() { 15 | let {ticket} = this.props; 16 | let departureTime = new Date(ticket.segment[0].departureTime).toLocaleDateString("en-US",dateConfig); 17 | let arrivalTime = new Date(ticket.segment[ticket.segment.length-1].arrivalTime).toLocaleDateString("en-US",dateConfig); 18 | 19 | let stops; 20 | if(ticket.segment.length === 2){ 21 | stops = '1 stop'; 22 | } else if(ticket.segment.length-1 > 1) { 23 | stops = ticket.segment.length-1 + ' stops'; 24 | } 25 | 26 | return( 27 |
    28 | {ticket.company} 29 | 30 | {ticket.segment[0].origin}{' '} 31 | {departureTime} 32 | 33 | 34 | ⇀ 35 | 36 | 37 | {ticket.segment[ticket.segment.length-1].destination}{' '} 38 | {arrivalTime} 39 | 40 | 41 | {stops} 42 | 43 | 44 | 45 | 46 |
    47 | ); 48 | } 49 | } 50 | TicketItem.propTypes = { 51 | ticket: PropTypes.shape({ 52 | id: PropTypes.string, 53 | company: PropTypes.string, 54 | points: PropTypes.number, 55 | duration: PropTypes.number, 56 | segment: PropTypes.array 57 | }), 58 | }; 59 | 60 | export default TicketItem; 61 | -------------------------------------------------------------------------------- /chapter 6/aircheap/app/constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | FETCH_AIRPORTS: 'fetch airports', 3 | FETCH_AIRPORTS_SUCCESS: 'fetch airports success', 4 | FETCH_AIRPORTS_ERROR: 'fetch airports error', 5 | CHOOSE_AIRPORT: 'choose airport', 6 | FETCH_TICKETS: 'fetch tickets', 7 | FETCH_TICKETS_SUCCESS: 'fetch tickets success', 8 | FETCH_TICKETS_ERROR: 'fetch tickets error' 9 | }; 10 | -------------------------------------------------------------------------------- /chapter 6/aircheap/app/stores/AirportStore.js: -------------------------------------------------------------------------------- 1 | import AppDispatcher from '../AppDispatcher'; 2 | import constants from '../constants'; 3 | import {ReduceStore} from 'flux/utils'; 4 | 5 | class AirportStore extends ReduceStore { 6 | getInitialState() { 7 | return []; 8 | } 9 | reduce(state, action){ 10 | switch (action.type) { 11 | 12 | case constants.FETCH_AIRPORTS_SUCCESS: 13 | return action.payload.response; 14 | 15 | default: 16 | return state; 17 | } 18 | } 19 | } 20 | export default new AirportStore(AppDispatcher); 21 | -------------------------------------------------------------------------------- /chapter 6/aircheap/app/stores/RouteStore.js: -------------------------------------------------------------------------------- 1 | import AppDispatcher from '../AppDispatcher'; 2 | import constants from '../constants'; 3 | import {MapStore} from 'flux/utils'; 4 | 5 | class RouteStore extends MapStore { 6 | reduce(state, action){ 7 | switch (action.type) { 8 | case constants.CHOOSE_AIRPORT: 9 | // action.target can be either “origin” or “destination” 10 | // action.code contains the selected airport code 11 | return state.set(action.target, action.code); 12 | default: 13 | return state; 14 | } 15 | } 16 | } 17 | export default new RouteStore(AppDispatcher); 18 | -------------------------------------------------------------------------------- /chapter 6/aircheap/app/stores/TicketStore.js: -------------------------------------------------------------------------------- 1 | import AppDispatcher from '../AppDispatcher'; 2 | import AirportActions from '../actions/AirportActionCreators'; 3 | import constants from '../constants'; 4 | import RouteStore from './RouteStore'; 5 | import {ReduceStore} from 'flux/utils'; 6 | 7 | class TicketStore extends ReduceStore { 8 | getInitialState() { 9 | return []; 10 | } 11 | reduce(state, action){ 12 | switch (action.type) { 13 | case constants.FETCH_TICKETS: 14 | return []; 15 | case constants.FETCH_TICKETS_SUCCESS: 16 | return action.payload.response; 17 | default: 18 | return state; 19 | } 20 | } 21 | } 22 | export default new TicketStore(AppDispatcher); 23 | -------------------------------------------------------------------------------- /chapter 6/aircheap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "babel-polyfill": "^6.7.4", 21 | "flux": "^2.1.1", 22 | "react": "^15.0.0", 23 | "react-autosuggest": "^2.1.11", 24 | "react-dom": "^15.0.0", 25 | "whatwg-fetch": "^0.11.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter 6/aircheap/public/airports.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "code": "ATL", 4 | "city": "Atlanta GA", 5 | "country": "US" 6 | }, 7 | { 8 | "code": "PEK", 9 | "city": "Beijing", 10 | "country": "CN" 11 | }, 12 | { 13 | "code": "LHR", 14 | "city": "London", 15 | "country": "GB" 16 | }, 17 | { 18 | "code": "ORD", 19 | "city": "Chicago IL", 20 | "country": "US" 21 | }, 22 | { 23 | "code": "HND", 24 | "city": "Tokyo", 25 | "country": "JP" 26 | }, 27 | { 28 | "code": "LAX", 29 | "city": "Los Angeles CA", 30 | "country": "US" 31 | }, 32 | { 33 | "code": "CDG", 34 | "city": "Paris", 35 | "country": "FR" 36 | }, 37 | { 38 | "code": "DFW", 39 | "city": "Dallas/Fort Worth TX", 40 | "country": "US" 41 | }, 42 | { 43 | "code": "FRA", 44 | "city": "Frankfurt", 45 | "country": "DE" 46 | }, 47 | { 48 | "code": "HKG", 49 | "city": "Hong Kong", 50 | "country": "HK" 51 | }, 52 | { 53 | "code": "DEN", 54 | "city": "Denver CO", 55 | "country": "US" 56 | }, 57 | { 58 | "code": "DXB", 59 | "city": "Dubai", 60 | "country": "AE" 61 | }, 62 | { 63 | "code": "CGK", 64 | "city": "Jakarta", 65 | "country": "ID" 66 | }, 67 | { 68 | "code": "AMS", 69 | "city": "Amsterdam", 70 | "country": "NL" 71 | }, 72 | { 73 | "code": "MAD", 74 | "city": "Madrid", 75 | "country": "ES" 76 | }, 77 | { 78 | "code": "BKK", 79 | "city": "Bangkok", 80 | "country": "TH" 81 | }, 82 | { 83 | "code": "JFK", 84 | "city": "New York NY", 85 | "country": "US" 86 | }, 87 | { 88 | "code": "SIN", 89 | "city": "Singapore", 90 | "country": "SG" 91 | }, 92 | { 93 | "code": "CAN", 94 | "city": "Guangzhou", 95 | "country": "CN" 96 | }, 97 | { 98 | "code": "LAS", 99 | "city": "Las Vegas NV", 100 | "country": "US" 101 | }, 102 | { 103 | "code": "PVG", 104 | "city": "Shanghai", 105 | "country": "CN" 106 | }, 107 | { 108 | "code": "SFO", 109 | "city": "San Francisco CA", 110 | "country": "US" 111 | }, 112 | { 113 | "code": "PHX", 114 | "city": "Phoenix AZ", 115 | "country": "US" 116 | }, 117 | { 118 | "code": "IAH", 119 | "city": "Houston TX", 120 | "country": "US" 121 | }, 122 | { 123 | "code": "CLT", 124 | "city": "Charlotte NC", 125 | "country": "US" 126 | }, 127 | { 128 | "code": "MIA", 129 | "city": "Miami FL", 130 | "country": "US" 131 | }, 132 | { 133 | "code": "MUC", 134 | "city": "Munich", 135 | "country": "DE" 136 | }, 137 | { 138 | "code": "KUL", 139 | "city": "Kuala Lumpur", 140 | "country": "MY" 141 | }, 142 | { 143 | "code": "FCO", 144 | "city": "Rome", 145 | "country": "IT" 146 | }, 147 | { 148 | "code": "IST", 149 | "city": "Istanbul", 150 | "country": "TR" 151 | }, 152 | { 153 | "code": "SYD", 154 | "city": "Sydney", 155 | "country": "AU" 156 | }, 157 | { 158 | "code": "MCO", 159 | "city": "Orlando FL", 160 | "country": "US" 161 | }, 162 | { 163 | "code": "ICN", 164 | "city": "Incheon", 165 | "country": "KR" 166 | }, 167 | { 168 | "code": "DEL", 169 | "city": "New Delhi", 170 | "country": "IN" 171 | }, 172 | { 173 | "code": "BCN", 174 | "city": "Barcelona", 175 | "country": "ES" 176 | }, 177 | { 178 | "code": "LGW", 179 | "city": "London", 180 | "country": "GB" 181 | }, 182 | { 183 | "code": "EWR", 184 | "city": "Newark NJ", 185 | "country": "US" 186 | }, 187 | { 188 | "code": "YYZ", 189 | "city": "Toronto ON", 190 | "country": "CA" 191 | }, 192 | { 193 | "code": "SHA", 194 | "city": "Shanghai", 195 | "country": "CN" 196 | }, 197 | { 198 | "code": "MSP", 199 | "city": "Minneapolis MN", 200 | "country": "US" 201 | }, 202 | { 203 | "code": "SEA", 204 | "city": "Seattle WA", 205 | "country": "US" 206 | }, 207 | { 208 | "code": "DTW", 209 | "city": "Detroit MI", 210 | "country": "US" 211 | }, 212 | { 213 | "code": "PHL", 214 | "city": "Philadelphia PA", 215 | "country": "US" 216 | }, 217 | { 218 | "code": "BOM", 219 | "city": "Mumbai", 220 | "country": "IN" 221 | }, 222 | { 223 | "code": "GRU", 224 | "city": "São Paulo", 225 | "country": "BR" 226 | }, 227 | { 228 | "code": "MNL", 229 | "city": "Manila", 230 | "country": "PH" 231 | }, 232 | { 233 | "code": "CTU", 234 | "city": "Chengdu", 235 | "country": "CN" 236 | }, 237 | { 238 | "code": "BOS", 239 | "city": "Boston MA", 240 | "country": "US" 241 | }, 242 | { 243 | "code": "SZX", 244 | "city": "Shenzhen", 245 | "country": "CN" 246 | }, 247 | { 248 | "code": "MEL", 249 | "city": "Melbourne", 250 | "country": "AU" 251 | }, 252 | { 253 | "code": "NRT", 254 | "city": "Tokyo", 255 | "country": "JP" 256 | }, 257 | { 258 | "code": "ORY", 259 | "city": "Paris", 260 | "country": "FR" 261 | }, 262 | { 263 | "code": "MEX", 264 | "city": "Mexico City", 265 | "country": "MX" 266 | }, 267 | { 268 | "code": "DME", 269 | "city": "Moscow", 270 | "country": "RU" 271 | }, 272 | { 273 | "code": "AYT", 274 | "city": "Antalya", 275 | "country": "TR" 276 | }, 277 | { 278 | "code": "TPE", 279 | "city": "Taipei", 280 | "country": "TW" 281 | }, 282 | { 283 | "code": "ZRH", 284 | "city": "Zurich", 285 | "country": "CH" 286 | }, 287 | { 288 | "code": "LGA", 289 | "city": "New York NY", 290 | "country": "US" 291 | }, 292 | { 293 | "code": "FLL", 294 | "city": "Fort Lauderdale FL", 295 | "country": "US" 296 | }, 297 | { 298 | "code": "IAD", 299 | "city": "Washington DC", 300 | "country": "US" 301 | }, 302 | { 303 | "code": "PMI", 304 | "city": "Palma De Mallorca", 305 | "country": "ES" 306 | }, 307 | { 308 | "code": "CPH", 309 | "city": "Copenhagen", 310 | "country": "DK" 311 | }, 312 | { 313 | "code": "SVO", 314 | "city": "Moscow", 315 | "country": "RU" 316 | }, 317 | { 318 | "code": "BWI", 319 | "city": "Baltimore MD", 320 | "country": "US" 321 | }, 322 | { 323 | "code": "KMG", 324 | "city": "Kunming", 325 | "country": "CN" 326 | }, 327 | { 328 | "code": "VIE", 329 | "city": "Vienna", 330 | "country": "AT" 331 | }, 332 | { 333 | "code": "OSL", 334 | "city": "Oslo", 335 | "country": "NO" 336 | }, 337 | { 338 | "code": "JED", 339 | "city": "Jeddah", 340 | "country": "SA" 341 | }, 342 | { 343 | "code": "BNE", 344 | "city": "Brisbane", 345 | "country": "AU" 346 | }, 347 | { 348 | "code": "SLC", 349 | "city": "Salt Lake City UT", 350 | "country": "US" 351 | }, 352 | { 353 | "code": "DUS", 354 | "city": "Düsseldorf", 355 | "country": "DE" 356 | }, 357 | { 358 | "code": "BOG", 359 | "city": "Bogota", 360 | "country": "CO" 361 | }, 362 | { 363 | "code": "MXP", 364 | "city": "Milan", 365 | "country": "IT" 366 | }, 367 | { 368 | "code": "JNB", 369 | "city": "Johannesburg", 370 | "country": "ZA" 371 | }, 372 | { 373 | "code": "ARN", 374 | "city": "Stockholm", 375 | "country": "SE" 376 | }, 377 | { 378 | "code": "MAN", 379 | "city": "Manchester", 380 | "country": "GB" 381 | }, 382 | { 383 | "code": "MDW", 384 | "city": "Chicago IL", 385 | "country": "US" 386 | }, 387 | { 388 | "code": "DCA", 389 | "city": "Washington DC", 390 | "country": "US" 391 | }, 392 | { 393 | "code": "BRU", 394 | "city": "Brussels", 395 | "country": "BE" 396 | }, 397 | { 398 | "code": "DUB", 399 | "city": "Dublin", 400 | "country": "IE" 401 | }, 402 | { 403 | "code": "GMP", 404 | "city": "Seoul", 405 | "country": "KR" 406 | }, 407 | { 408 | "code": "DOH", 409 | "city": "Doha", 410 | "country": "QA" 411 | }, 412 | { 413 | "code": "STN", 414 | "city": "London", 415 | "country": "GB" 416 | }, 417 | { 418 | "code": "HGH", 419 | "city": "Hangzhou", 420 | "country": "CN" 421 | }, 422 | { 423 | "code": "CJU", 424 | "city": "Jeju", 425 | "country": "KR" 426 | }, 427 | { 428 | "code": "YVR", 429 | "city": "Vancouver BC", 430 | "country": "CA" 431 | }, 432 | { 433 | "code": "TXL", 434 | "city": "Berlin", 435 | "country": "DE" 436 | }, 437 | { 438 | "code": "SAN", 439 | "city": "San Diego CA", 440 | "country": "US" 441 | }, 442 | { 443 | "code": "TPA", 444 | "city": "Tampa FL", 445 | "country": "US" 446 | }, 447 | { 448 | "code": "CGH", 449 | "city": "São Paulo", 450 | "country": "BR" 451 | }, 452 | { 453 | "code": "BSB", 454 | "city": "Brasilia", 455 | "country": "BR" 456 | }, 457 | { 458 | "code": "CTS", 459 | "city": "Sapporo", 460 | "country": "JP" 461 | }, 462 | { 463 | "code": "XMN", 464 | "city": "Xiamen", 465 | "country": "CN" 466 | }, 467 | { 468 | "code": "RUH", 469 | "city": "Riyadh", 470 | "country": "SA" 471 | }, 472 | { 473 | "code": "FUK", 474 | "city": "Fukuoka", 475 | "country": "JP" 476 | }, 477 | { 478 | "code": "GIG", 479 | "city": "Rio De Janeiro", 480 | "country": "BR" 481 | }, 482 | { 483 | "code": "HEL", 484 | "city": "Helsinki", 485 | "country": "FI" 486 | }, 487 | { 488 | "code": "LIS", 489 | "city": "Lisbon", 490 | "country": "PT" 491 | }, 492 | { 493 | "code": "ATH", 494 | "city": "Athens", 495 | "country": "GR" 496 | }, 497 | { 498 | "code": "AKL", 499 | "city": "Auckland", 500 | "country": "NZ" 501 | }, 502 | { 503 | "code": "CLE", 504 | "city": "Cleveland", 505 | "country": "US" 506 | }, 507 | { 508 | "code": "PDX", 509 | "city": "Portland", 510 | "country": "US" 511 | }, 512 | { 513 | "code": "STL", 514 | "city": "St Louis", 515 | "country": "US" 516 | }, 517 | { 518 | "code": "HOU", 519 | "city": "Houston TX", 520 | "country": "US" 521 | }, 522 | { 523 | "code": "OAK", 524 | "city": "Oakland", 525 | "country": "US" 526 | }, 527 | { 528 | "code": "MCI", 529 | "city": "Kansas City", 530 | "country": "US" 531 | }, 532 | { 533 | "code": "BNA", 534 | "city": "Nashville", 535 | "country": "US" 536 | }, 537 | { 538 | "code": "AUS", 539 | "city": "Austin", 540 | "country": "US" 541 | }, 542 | { 543 | "code": "RDU", 544 | "city": "Raleigh/Durham", 545 | "country": "US" 546 | }, 547 | { 548 | "code": "SMF", 549 | "city": "Sacramento", 550 | "country": "US" 551 | }, 552 | { 553 | "code": "SNA", 554 | "city": "Santa Ana", 555 | "country": "US" 556 | }, 557 | { 558 | "code": "CUN", 559 | "city": "Cancún", 560 | "country": "MX" 561 | }, 562 | { 563 | "code": "SJU", 564 | "city": "San Juan", 565 | "country": "PR" 566 | }, 567 | { 568 | "code": "MBJ", 569 | "city": "Montego Bay", 570 | "country": "JM" 571 | }, 572 | { 573 | "code": "PUJ", 574 | "city": "Punta Cana", 575 | "country": "DO" 576 | }, 577 | { 578 | "code": "NAS", 579 | "city": "Nassau", 580 | "country": "BS" 581 | } 582 | ] 583 | -------------------------------------------------------------------------------- /chapter 6/aircheap/public/flights.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "fc704c16fd79", 4 | "company": "US Airlines", 5 | "points": 25000, 6 | "duration": 590, 7 | "segment": [ 8 | { 9 | "duration": 590, 10 | "departureTime": "2016-10-10T21:30-03:00", 11 | "arrivalTime": "2016-10-11T06:20-04:00", 12 | "origin": "GRU", 13 | "destination": "JFK" 14 | } 15 | ] 16 | }, 17 | { 18 | "id": "3fe21e46fd78", 19 | "company": "Dalta", 20 | "points": 20000, 21 | "duration": 862, 22 | "segment": [ 23 | { 24 | "duration": 635, 25 | "departureTime": "2016-10-16T20:25-03:00", 26 | "arrivalTime": "2016-10-17T06:00-04:00", 27 | "origin": "GRU", 28 | "destination": "YYZ", 29 | "connectionDuration": 125 30 | }, 31 | { 32 | "duration": 102, 33 | "departureTime": "2016-10-17T08:05-04:00", 34 | "arrivalTime": "2016-10-17T09:47-04:00", 35 | "origin": "YYZ", 36 | "destination": "JFK" 37 | } 38 | ] 39 | }, 40 | { 41 | "id": "8bf2b3d7be09", 42 | "company": "Aviana", 43 | "points": 17000, 44 | "duration": 1050, 45 | "segment": [ 46 | { 47 | "duration": 515, 48 | "departureTime": "2016-10-10T21:25-03:00", 49 | "arrivalTime": "2016-10-11T05:00-04:00", 50 | "origin": "GRU", 51 | "destination": "MIA", 52 | "connectionDuration": 145 53 | }, 54 | { 55 | "duration": 192, 56 | "departureTime": "2016-10-11T07:25-04:00", 57 | "arrivalTime": "2016-10-11T10:37-04:00", 58 | "origin": "MIA", 59 | "destination": "YYZ", 60 | "connectionDuration": 98 61 | }, 62 | { 63 | "duration": 100, 64 | "departureTime": "2016-10-11T12:15-04:00", 65 | "arrivalTime": "2016-10-11T13:55-04:00", 66 | "origin": "YYZ", 67 | "destination": "JFK" 68 | } 69 | ] 70 | } 71 | ] 72 | -------------------------------------------------------------------------------- /chapter 6/aircheap/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 6/aircheap/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/pro-react/a1d7f723a2acbb365f983f601d906152a1e08723/chapter 6/aircheap/public/logo.png -------------------------------------------------------------------------------- /chapter 6/aircheap/public/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | body { 5 | margin: 0; 6 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 7 | } 8 | header { 9 | padding-top: 10px; 10 | border-bottom: 1px solid #ccc; 11 | border-top: 4px solid #08516E; 12 | height: 115px; 13 | background-color: #f6f6f6; 14 | } 15 | p { 16 | margin:0; 17 | font-size: 10px; 18 | } 19 | .header-brand { 20 | text-align: center; 21 | } 22 | .header-route { 23 | margin-top: 10px; 24 | margin-left: calc(50% - 205px) 25 | } 26 | 27 | .react-autosuggest { 28 | position: relative; 29 | float: left; 30 | margin-right: 5px; 31 | } 32 | .react-autosuggest input { 33 | width: 200px; 34 | height: 30px; 35 | padding: 14px 10px; 36 | font-size: 13px; 37 | border: 1px solid #aaaaaa; 38 | border-radius: 4px; 39 | } 40 | .react-autosuggest input[aria-expanded="true"] { 41 | border-bottom-left-radius: 0; 42 | border-bottom-right-radius: 0; 43 | } 44 | .react-autosuggest input:focus { 45 | outline: none; 46 | } 47 | .react-autosuggest__suggestions { 48 | position: absolute; 49 | top: 29px; 50 | width: 200px; 51 | margin: 0; 52 | padding: 0; 53 | list-style-type: none; 54 | border: 1px solid #aaaaaa; 55 | background-color: #fff; 56 | font-size: 13px; 57 | border-bottom-left-radius: 4px; 58 | border-bottom-right-radius: 4px; 59 | z-index: 2; 60 | } 61 | .react-autosuggest__suggestions-section-suggestions { 62 | margin: 0; 63 | padding: 0; 64 | list-style-type: none; 65 | } 66 | .react-autosuggest__suggestion { 67 | cursor: pointer; 68 | padding: 10px 10px; 69 | } 70 | .react-autosuggest__suggestion--focused { 71 | background-color: #ddd; 72 | } 73 | .ticket { 74 | padding: 20px 10px; 75 | background-color: #fafafa; 76 | margin: 5px; 77 | border: 1px solid #e5e5df; 78 | border-radius: 3px; 79 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); 80 | } 81 | .ticket span { 82 | display: inline-block; 83 | } 84 | .ticket-company { 85 | font-weight: bold; 86 | font-style: italic; 87 | width: 13%; 88 | } 89 | .ticket-location { 90 | text-align: center; 91 | width: 29%; 92 | } 93 | .ticket-separator { 94 | text-align: center; 95 | width: 6%; 96 | } 97 | .ticket-connection { 98 | text-align: center; 99 | width: 10%; 100 | } 101 | .ticket-points { 102 | width: 13%; 103 | text-align: right; 104 | } 105 | -------------------------------------------------------------------------------- /chapter 6/aircheap/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/README.md: -------------------------------------------------------------------------------- 1 | Flux Bank 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; import { render } from 'react-dom'; import {Container} from 'flux/utils'; import BankBalanceStore from './BankBalanceStore'; import BankRewardsStore from './BankRewardsStore'; import BankActions from './BankActions'; class App extends Component { constructor(){ super(...arguments); BankActions.createAccount(); } deposit() { BankActions.depositIntoAccount(Number(this.refs.ammount.value)); this.refs.ammount.value = ''; } withdraw() { BankActions.withdrawFromAccount(Number(this.refs.ammount.value)); this.refs.ammount.value = ''; } render(){ return (
    FluxTrust Bank

    Your balance is ${(this.state.balance).toFixed(2)}

    Your Points Rewards Tier is {this.state.rewardsTier}


    ); } } App.getStores = () => ([BankBalanceStore, BankRewardsStore]); App.calculateState = (prevState) => ({ balance: BankBalanceStore.getState(), rewardsTier: BankRewardsStore.getState() }); const AppContainer = Container.create(App); render(, document.getElementById('root')); -------------------------------------------------------------------------------- /chapter 6/fluxbank/app/AppDispatcher.js: -------------------------------------------------------------------------------- 1 | import {Dispatcher} from 'flux'; 2 | 3 | class AppDispatcher extends Dispatcher{ 4 | dispatch(action = {}) { 5 | console.log("Dispatched", action); 6 | super.dispatch(action); 7 | } 8 | } 9 | 10 | export default new AppDispatcher(); 11 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/app/BankActions.js: -------------------------------------------------------------------------------- 1 | import AppDispatcher from './AppDispatcher'; 2 | import bankConstants from './constants'; 3 | 4 | let BankActions = { 5 | 6 | /** 7 | * Create an account with an empty value 8 | */ 9 | createAccount() { 10 | AppDispatcher.dispatch({ 11 | type: bankConstants.CREATED_ACCOUNT, 12 | ammount: 0 13 | }); 14 | }, 15 | 16 | /** 17 | * @param {number} ammount to whithdraw 18 | */ 19 | depositIntoAccount(ammount) { 20 | AppDispatcher.dispatch({ 21 | type: bankConstants.DEPOSITED_INTO_ACCOUNT, 22 | ammount: ammount 23 | }); 24 | }, 25 | 26 | /** 27 | * @param {number} ammount to whithdraw 28 | */ 29 | withdrawFromAccount(ammount) { 30 | AppDispatcher.dispatch({ 31 | type: bankConstants.WITHDREW_FROM_ACCOUNT, 32 | ammount: ammount 33 | }); 34 | } 35 | 36 | }; 37 | 38 | export default BankActions; 39 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/app/BankBalanceStore.js: -------------------------------------------------------------------------------- 1 | import AppDispatcher from './AppDispatcher'; 2 | import bankConstants from './constants'; 3 | import {ReduceStore} from 'flux/utils'; 4 | 5 | class BankBalanceStore extends ReduceStore { 6 | getInitialState() { 7 | return 0; 8 | } 9 | 10 | reduce(state, action){ 11 | switch (action.type) { 12 | 13 | case bankConstants.CREATED_ACCOUNT: 14 | return 0; 15 | 16 | case bankConstants.DEPOSITED_INTO_ACCOUNT: 17 | return state + action.ammount; 18 | 19 | case bankConstants.WITHDREW_FROM_ACCOUNT: 20 | return state - action.ammount; 21 | 22 | default: 23 | return state; 24 | } 25 | } 26 | } 27 | 28 | export default new BankBalanceStore(AppDispatcher); 29 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/app/BankRewardsStore.js: -------------------------------------------------------------------------------- 1 | import AppDispatcher from './AppDispatcher'; 2 | import BankBalanceStore from './BankBalanceStore' 3 | import bankConstants from './constants'; 4 | import {ReduceStore} from 'flux/utils'; 5 | 6 | class BankRewardsStore extends ReduceStore { 7 | getInitialState() { 8 | return 'Basic'; 9 | } 10 | reduce(state, action){ 11 | this.getDispatcher().waitFor([ 12 | BankBalanceStore.getDispatchToken() 13 | ]); 14 | 15 | if (action.type === bankConstants.DEPOSITED_INTO_ACCOUNT || 16 | action.type === bankConstants.WITHDREW_FROM_ACCOUNT ) { 17 | let balance = BankBalanceStore.getState(); 18 | if (balance < 5000) 19 | return 'Basic'; 20 | else if (balance < 10000) 21 | return 'Silver'; 22 | else if (balance < 50000) 23 | return 'Gold'; 24 | else 25 | return 'Platinum'; 26 | } 27 | return state; 28 | } 29 | } 30 | export default new BankRewardsStore(AppDispatcher); 31 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/app/constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | CREATED_ACCOUNT: 'created account', 3 | WITHDREW_FROM_ACCOUNT: 'withdrew from account', 4 | DEPOSITED_INTO_ACCOUNT: 'deposited into account' 5 | }; 6 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "fbemitter": "^2.0.2", 21 | "flux": "^2.1.1", 22 | "react": "^15.0.0", 23 | "react-dom": "^15.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font: 16px/1 sans-serif; 4 | background-color: #318435; 5 | color: #fff; 6 | text-align: center; 7 | } 8 | header{ 9 | width:100%; 10 | padding: 15px; 11 | text-align: center; 12 | background-color: #000; 13 | } 14 | h1{ 15 | font-size: 18px; 16 | } 17 | h2{ 18 | font-size: 16px; 19 | } 20 | .atm{ 21 | width: 200px; 22 | height: 100px; 23 | border-radius: 10px; 24 | background-color: #000; 25 | text-align: center; 26 | margin: 10px auto 0 auto; 27 | padding: 20px; 28 | } 29 | .atm input{ 30 | font-size:25px; 31 | width: 180px 32 | } 33 | .atm button{ 34 | margin: 5px; 35 | padding: 20px; 36 | } 37 | -------------------------------------------------------------------------------- /chapter 6/fluxbank/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 7/clock/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cassio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /chapter 7/clock/README.md: -------------------------------------------------------------------------------- 1 | Clock profiling React sample code 2 | ============ 3 | 4 | **Install** 5 | ``` 6 | npm install 7 | ``` 8 | 9 | **Start the application in development mode** 10 | ``` 11 | npm start 12 | ``` 13 | 14 | Open http://localhost:8080 in your browser. 15 | -------------------------------------------------------------------------------- /chapter 7/clock/app/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; import { render } from 'react-dom'; import Clock from './Clock'; import Perf from 'react-addons-perf'; class App extends Component { constructor(){ super(...arguments); this.state = this.getTime(); } componentDidMount(){ setInterval(()=>{ this.setState(this.getTime()); },10); } getTime(){ let now = new Date(); return { hours: now.getHours(), minutes: now.getMinutes(), seconds: now.getSeconds(), tenths: parseInt(now.getMilliseconds()/10), }; } render(){ let clocks=[]; for (var i = 0; i < 200; i++) { clocks.push() } return (
    {clocks}
    ); } } Perf.start() render(, document.getElementById("root")); setTimeout(()=>{ Perf.stop(); Perf.printWasted(); },2000) -------------------------------------------------------------------------------- /chapter 7/clock/app/Clock.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import Digit from './Digit'; 3 | 4 | class Clock extends Component { 5 | render() { 6 | return( 7 |
    8 | {' : '} 9 | {' : '} 10 | {' . '} 11 | 12 |
    13 | ); 14 | } 15 | } 16 | 17 | Clock.propTypes = { 18 | hours: PropTypes.number.isRequired, 19 | minutes: PropTypes.number.isRequired, 20 | seconds: PropTypes.number.isRequired, 21 | tenths: PropTypes.number.isRequired 22 | } 23 | 24 | export default Clock; 25 | -------------------------------------------------------------------------------- /chapter 7/clock/app/Digit.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class Digit extends Component { 4 | // shouldComponentUpdate(nextProps, nextState) { 5 | // return nextProps.value !== this.props.value; 6 | // } 7 | 8 | render() { 9 | let digitStyle={ 10 | display:'inline-block', 11 | fontSize: 20, 12 | padding: 10, 13 | margin: 5, 14 | background: '#eeeeee' 15 | }; 16 | 17 | let displayValue; 18 | if(this.props.value < 10){ 19 | displayValue = '0' + this.props.value; 20 | } else { 21 | displayValue = this.props.value; 22 | } 23 | 24 | return ( 25 |
    {displayValue}
    26 | ); 27 | } 28 | } 29 | 30 | Digit.propTypes = { 31 | value: PropTypes.number.isRequired 32 | } 33 | 34 | export default Digit; 35 | -------------------------------------------------------------------------------- /chapter 7/clock/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "0.1.4", 4 | "description": "React application boilerplate", 5 | "author": "Cássio Zen", 6 | "license": "ISC", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "react": "^15.0.0", 21 | "react-addons-perf": "^15.0.0", 22 | "react-dom": "^15.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter 7/clock/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Performance 5 | 6 | 15 | 16 | 17 |
    18 |
    19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapter 7/clock/public/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font: 16px/1 sans-serif; 4 | } 5 | #root { 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /chapter 7/clock/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | -------------------------------------------------------------------------------- /chapter 8/helloexpress/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /chapter 8/helloexpress/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helloexpress", 3 | "version": "0.0.2", 4 | "description": "Hello world sample application in Node.js + Express", 5 | "scripts": { 6 | "start": "DEBUG=express:* babel-node --debug server.js" 7 | }, 8 | "author": "Cássio Zen", 9 | "license": "ISC", 10 | "dependencies": { 11 | "babel-core": "~6.7.*", 12 | "babel-preset-es2015": "~6.6.*", 13 | "ejs": "^2.4.1", 14 | "express": "^4.13.4" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter 8/helloexpress/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | const app = express(); 4 | 5 | app.set('view engine', 'ejs'); 6 | app.set('views', './views') 7 | 8 | app.get('/', (request, response) => { 9 | response.render('index',{message:'Hello World (from express)'}); 10 | }); 11 | 12 | app.listen(3000, ()=>{ 13 | console.log('Express app listening on port 3000'); 14 | }); 15 | -------------------------------------------------------------------------------- /chapter 8/helloexpress/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Express Template 6 | 7 | 8 |

    <%= message %>

    9 | 10 | 11 | -------------------------------------------------------------------------------- /chapter 8/universal-react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015","react"] 3 | } 4 | -------------------------------------------------------------------------------- /chapter 8/universal-react/app/components/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { Link } from 'react-router' 3 | 4 | class App extends Component { 5 | render(){ 6 | return( 7 |
    8 | 12 |
    13 | {this.props.children} 14 |
    15 |
    16 | ) 17 | } 18 | }; 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /chapter 8/universal-react/app/components/ContactItem.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | 3 | 4 | class ContactItem extends Component { 5 | render() { 6 | return
  • {this.props.name} - {this.props.email}
  • 7 | } 8 | } 9 | ContactItem.propTypes = { 10 | name: PropTypes.string.isRequired, 11 | email: PropTypes.string.isRequired, 12 | } 13 | 14 | export default ContactItem; 15 | -------------------------------------------------------------------------------- /chapter 8/universal-react/app/components/ContactList.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import ContactItem from './ContactItem'; 3 | 4 | class ContactList extends React.Component { 5 | render(){ 6 | var filteredContacts = this.props.contacts.filter( 7 | (contact) => contact.name.indexOf(this.props.filterText) !== -1 8 | ); 9 | return( 10 |
      11 | {filteredContacts.map( 12 | (contact) => 15 | )} 16 |
    17 | ) 18 | } 19 | } 20 | ContactList.propTypes = { 21 | contacts: React.PropTypes.arrayOf(React.PropTypes.object), 22 | filterText: React.PropTypes.string.isRequired 23 | } 24 | 25 | 26 | export default ContactList; 27 | -------------------------------------------------------------------------------- /chapter 8/universal-react/app/components/ContactsApp.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; import fetch from 'isomorphic-fetch'; 2 | import ContactList from './ContactList'; 3 | import SearchBar from './SearchBar'; 4 | 5 | class ContactsApp extends Component { 6 | constructor(){ 7 | super(...arguments); 8 | this.state = { 9 | contacts: this.props.initialData || [], 10 | filterText: '' 11 | } 12 | } 13 | componentDidMount(){ 14 | if (!this.props.initialData) { 15 | ContactsApp.requestInitialData().then(contacts => { 16 | this.setState({ contacts }); 17 | }); 18 | } 19 | } 20 | handleUserInput(searchTerm){ 21 | this.setState({filterText:searchTerm}) 22 | } 23 | render(){ 24 | return( 25 |
    26 | 27 | 28 |
    29 | ) 30 | } 31 | }; 32 | 33 | ContactsApp.propTypes = { 34 | initialData: PropTypes.any 35 | }; 36 | 37 | ContactsApp.requestInitialData = () => { 38 | return fetch('http://localhost:3000/contacts.json') 39 | .then((response) => response.json()); 40 | }; 41 | 42 | export default ContactsApp; 43 | -------------------------------------------------------------------------------- /chapter 8/universal-react/app/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class Home extends Component { 4 | render(){ 5 | return

    Home

    ; 6 | } 7 | }; 8 | 9 | export default Home; 10 | -------------------------------------------------------------------------------- /chapter 8/universal-react/app/components/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | 3 | class SearchBar extends Component { 4 | handleChange(event){ 5 | this.props.onUserInput(event.target.value) 6 | } 7 | render(){ 8 | return 12 | } 13 | } 14 | SearchBar.propTypes = { 15 | onUserInput: PropTypes.func.isRequired, 16 | filterText: PropTypes.string.isRequired 17 | } 18 | export default SearchBar; 19 | -------------------------------------------------------------------------------- /chapter 8/universal-react/app/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import App from './components/App'; 4 | import Home from './components/Home'; 5 | import ContactsApp from './components/ContactsApp'; 6 | 7 | export default ( 8 | 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /chapter 8/universal-react/browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Router } from 'react-router'; 4 | import { createHistory } from 'history'; 5 | import routes from './app/routes'; 6 | 7 | let handleCreateElement = (Component, props) => { 8 | if(Component.hasOwnProperty('requestInitialData')){ 9 | let initialData = document.getElementById('initial-data').textContent; 10 | if(initialData.length>0){ 11 | initialData = JSON.parse(initialData); 12 | } 13 | return ; 14 | } else { 15 | return ; 16 | } 17 | } 18 | 19 | render( 20 | {routes}, 21 | document.getElementById('root') 22 | ) 23 | -------------------------------------------------------------------------------- /chapter 8/universal-react/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Isomorphic React 6 | 7 | 8 |
    <%- content %>
    9 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /chapter 8/universal-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "server": "DEBUG=express:* babel-node --debug server.js", 7 | "build": "NODE_ENV=production webpack -p", 8 | "start": "npm run build && npm run server" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "babel-core": "~6.7.*", 15 | "babel-loader": "~6.2.*", 16 | "babel-preset-es2015": "~6.6.*", 17 | "babel-preset-react": "~6.5.*", 18 | "ejs": "^2.4.1", 19 | "express": "^4.13.4", 20 | "history": "^1.13.0", 21 | "isomorphic-fetch": "^2.2.1", 22 | "react": "^15.0.0", 23 | "react-dom": "^15.0.0", 24 | "react-router": "^1.0.0", 25 | "webpack": "^1.12.*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter 8/universal-react/public/contacts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Cassio Zen", 4 | "email": "cassiozen@gmail.com" 5 | }, 6 | { 7 | "name": "Dan Abramov", 8 | "email": "gaearon@somewhere.com" 9 | }, 10 | { 11 | "name": "Pete Hunt", 12 | "email": "floydophone@somewhere.com" 13 | }, 14 | { 15 | "name": "Paul O’Shannessy", 16 | "email": "zpao@somewhere.com" 17 | }, 18 | { 19 | "name": "Ryan Florence", 20 | "email": "rpflorence@somewhere.com" 21 | }, 22 | { 23 | "name": "Sebastian Markbage", 24 | "email": "sebmarkbage@here.com" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /chapter 8/universal-react/server.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import express from 'express'; 3 | import React from 'react'; 4 | import { renderToString } from 'react-dom/server'; 5 | import { match, RoutingContext } from 'react-router'; 6 | import routes from './app/routes'; 7 | 8 | const app = express(); 9 | app.set('views', './'); 10 | app.set('view engine', 'ejs'); 11 | app.use(express.static(__dirname + '/public')); 12 | 13 | const contacts = JSON.parse(fs.readFileSync(__dirname + '/public/contacts.json', 'utf8')); 14 | 15 | // Helper function: Loop through all components in the renderProps object 16 | // and returns a new object with the desired key 17 | let getPropsFromRoute = ({routes}, componentProps) => { 18 | let props = {}; 19 | let lastRoute = routes[routes.length - 1]; 20 | routes.reduceRight((prevRoute, currRoute) => { 21 | componentProps.forEach(componentProp => { 22 | if (!props[componentProp] && currRoute.component[componentProp]) { 23 | props[componentProp] = currRoute.component[componentProp]; 24 | } 25 | }); 26 | }, lastRoute); 27 | return props; 28 | }; 29 | 30 | let renderRoute = (response, renderProps) => { 31 | // Loop through renderProps object looking for 'requestInitialData' 32 | let routeProps = getPropsFromRoute(renderProps, ['requestInitialData']); 33 | if (routeProps.requestInitialData) { 34 | // If one of the components implements 'requestInitialData', invoke it. 35 | routeProps.requestInitialData().then((data)=>{ 36 | // Ovewrite the react-router create element function 37 | // and pass the pre-fetched data as initialData props 38 | let handleCreateElement = (Component, props) =>( 39 | 40 | ); 41 | // Render the template with RoutingContext and loaded data. 42 | response.render('index',{ 43 | reactInitialData: JSON.stringify(data), 44 | content: renderToString( 45 | 46 | ) 47 | }); 48 | }); 49 | } else { 50 | // No components in this route implements 'requestInitialData'. 51 | // Simply render the template with RoutingContext and no initialData. 52 | response.render('index',{ 53 | reactInitialData: null, 54 | content: renderToString() 55 | }); 56 | } 57 | }; 58 | 59 | app.get('*', (request, response) => { 60 | match({ routes, location: request.url }, (error, redirectLocation, renderProps) => { 61 | if (error) { 62 | response.status(500).send(error.message); 63 | } else if (redirectLocation) { 64 | response.redirect(302, redirectLocation.pathname + redirectLocation.search); 65 | } else if (renderProps) { 66 | renderRoute(response, renderProps); 67 | } else { 68 | response.status(404).send('Not found'); 69 | } 70 | }); 71 | }); 72 | 73 | app.listen(3000, ()=>{ 74 | console.log('Express app listening on port 3000'); 75 | }); 76 | -------------------------------------------------------------------------------- /chapter 8/universal-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = { 4 | entry: [ 5 | './browser.js' 6 | ], 7 | output: { 8 | path: './public', 9 | filename: "bundle.js" 10 | }, 11 | module: { 12 | loaders: [{ 13 | test: /\.jsx?$/, 14 | loader: 'babel' 15 | }] 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /chapter 9/checkboxWithLabel/CheckboxWithLabel.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class CheckboxWithLabel extends Component { 4 | 5 | constructor() { 6 | super(...arguments); 7 | this.state = {isChecked: false}; 8 | this.onChange = this.onChange.bind(this); 9 | } 10 | 11 | onChange() { 12 | this.setState({isChecked: !this.state.isChecked}); 13 | } 14 | 15 | render() { 16 | return ( 17 | 23 | ); 24 | } 25 | } 26 | 27 | export default CheckboxWithLabel; 28 | -------------------------------------------------------------------------------- /chapter 9/checkboxWithLabel/__tests__/CheckboxWithLabel_shallow_test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import TestUtils from 'react-addons-test-utils'; 6 | import ShallowTestUtils from 'react-shallow-testutils'; 7 | 8 | const shallowRenderer = TestUtils.createRenderer(); 9 | const CheckboxWithLabel = require('../CheckboxWithLabel'); 10 | 11 | describe('CheckboxWithLabel', () => { 12 | 13 | // Render a checkbox with label in the document 14 | shallowRenderer.render(); 15 | 16 | let checkbox = shallowRenderer.getRenderOutput(); 17 | const component = ShallowTestUtils.getMountedInstance(shallowRenderer); 18 | 19 | it('defaults to unchecked and Off label', () => { 20 | const expectedChildren = [ 21 | , 22 | "Off" 23 | ]; 24 | expect(checkbox.props.children).toEqual(expectedChildren); 25 | }); 26 | 27 | 28 | it('changes the label after click', () => { 29 | component.onChange(); 30 | 31 | // Updates the render 32 | checkbox = shallowRenderer.getRenderOutput(); 33 | 34 | expect(checkbox.props.children[1]).toEqual('On'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /chapter 9/checkboxWithLabel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testing-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "ISC", 9 | "devDependencies": { 10 | "babel-jest": "^5.3.0", 11 | "jest-cli": "^0.6.1", 12 | "react": "^0.14.1", 13 | "react-addons-test-utils": "^0.14.1", 14 | "react-dom": "^0.14.1" 15 | }, 16 | "scripts": { 17 | "test": "jest" 18 | }, 19 | "jest": { 20 | "scriptPreprocessor": "/node_modules/babel-jest", 21 | "unmockedModulePathPatterns": [ 22 | "/node_modules/react", 23 | "/node_modules/react-dom", 24 | "/node_modules/react-addons-test-utils", 25 | "/node_modules/fbjs" 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /kanban-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Dependency directory 6 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 7 | node_modules 8 | 9 | # OS specific 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /kanban-app/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cássio Zen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /kanban-app/README.md: -------------------------------------------------------------------------------- 1 | Kanban React.js App - Chapter 1 2 | ================================= 3 | 4 | Kanban-style project management tool built throughout the Pro React book. End of chapter 1. 5 | 6 | ### Summary 7 | 8 | Implemented the basic structure of the project. All data is hard-coded and React warns about missing `key` props (which are implemented in chapter 2). 9 | 10 | ![end_of_chapter1](https://cloud.githubusercontent.com/assets/33676/10971478/9db2d9d2-83bb-11e5-8603-5a19b21376a8.png) 11 | 12 | 13 | ### How the repository is organized 14 | 15 | You are in the Chapter 1 Branch. 16 | 17 | The repository is organized in branches: Each branch corresponds to the end of a specific chapter. The master branch contains the final source code. 18 | 19 | After cloning and fetching all of the remote branches, you can switch branches using `git checkout`, for example: 20 | 21 | ``` 22 | git clone git@github.com:pro-react/kanban-app.git 23 | git fetch --all 24 | git checkout chapter3 25 | ``` 26 | 27 | ### Usage 28 | 29 | **Install** 30 | ``` 31 | npm install 32 | ``` 33 | 34 | **Start the application** 35 | ``` 36 | npm start 37 | ``` 38 | 39 | Open http://localhost:8080 in your browser. 40 | -------------------------------------------------------------------------------- /kanban-app/app/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; import {render} from 'react-dom'; import KanbanBoard from './KanbanBoard'; let cardsList = [ { id: 1, title: "Read the Book", description: "I should read the whole book", status: "in-progress", tasks: [] }, { id: 2, title: "Write some code", description: "Code along with the samples in the book", status: "todo", tasks: [ { id: 1, name: "ContactList Example", done: true }, { id: 2, name: "Kanban Example", done: false }, { id: 3, name: "My own experiments", done: false } ] } ]; render(, document.getElementById('root')); -------------------------------------------------------------------------------- /kanban-app/app/Card.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import CheckList from './CheckList'; 3 | 4 | class Card extends Component { 5 | constructor() { 6 | super(...arguments); 7 | this.state = { 8 | showDetails: false 9 | } 10 | }; 11 | 12 | render() { 13 | let cardDetails; 14 | if (this.state.showDetails) { 15 | cardDetails = ( 16 |
    17 | {this.props.description} 18 | 19 |
    20 | ); 21 | }; 22 | return ( 23 |
    24 |
    this.setState({showDetails: !this.state.showDetails}) 26 | }>{this.props.title}
    27 | {cardDetails} 28 |
    29 | ); 30 | } 31 | } 32 | 33 | export default Card; 34 | -------------------------------------------------------------------------------- /kanban-app/app/CheckList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class CheckList extends Component { 4 | render() { 5 | let tasks = this.props.tasks.map((task) => ( 6 |
  • 7 | 8 | {task.name} 9 | 10 |
  • 11 | )); 12 | 13 | return ( 14 |
    15 |
      {tasks}
    16 |
    17 | ); 18 | } 19 | } 20 | 21 | export default CheckList; 22 | -------------------------------------------------------------------------------- /kanban-app/app/KanbanBoard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import List from './List'; 3 | 4 | class KanbanBoard extends Component { 5 | render(){ 6 | return ( 7 |
    8 | card.status === "todo")} /> 11 | card.status === "in-progress")} /> 14 | card.status === "done")} /> 17 |
    18 | ); 19 | } 20 | }; 21 | 22 | export default KanbanBoard; 23 | -------------------------------------------------------------------------------- /kanban-app/app/List.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Card from './Card'; 3 | 4 | class List extends Component { 5 | render() { 6 | var cards = this.props.cards.map((card) => { 7 | return 11 | }); 12 | 13 | return ( 14 |
    15 |

    {this.props.title}

    16 | {cards} 17 |
    18 | ); 19 | } 20 | }; 21 | 22 | export default List; 23 | -------------------------------------------------------------------------------- /kanban-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kanban-app", 3 | "version": "0.1.0", 4 | "description": "Pro React sample kanban app project", 5 | "author": "Cássio Zen", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "webpack-dev-server --progress", 9 | "build": "NODE_ENV=production webpack -p --progress --colors" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~6.7.*", 13 | "babel-loader": "~6.2.*", 14 | "babel-preset-es2015": "~6.6.*", 15 | "babel-preset-react": "~6.5.*", 16 | "webpack": "~1.12.*", 17 | "webpack-dev-server": "~1.14.*" 18 | }, 19 | "dependencies": { 20 | "react": "^15.0.0", 21 | "react-dom": "^15.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /kanban-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pro-React Kanban 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /kanban-app/public/styles.css: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing: border-box; 3 | } 4 | 5 | html,body,#root { 6 | height:100%; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | body { 12 | background: #eee; 13 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 14 | } 15 | 16 | h1{ 17 | font-weight: 200; 18 | color: #3b414c; 19 | font-size: 20px; 20 | } 21 | 22 | ul { 23 | list-style-type: none; 24 | padding: 0; 25 | margin: 0; 26 | } 27 | 28 | .app { 29 | white-space: nowrap; 30 | height:100%; 31 | } 32 | 33 | .list { 34 | position: relative; 35 | display: inline-block; 36 | vertical-align: top; 37 | white-space: normal; 38 | height: 100%; 39 | width: 33%; 40 | padding: 0 20px; 41 | overflow: auto; 42 | } 43 | 44 | .list:not(:last-child):after{ 45 | content: ""; 46 | position: absolute; 47 | top: 0; 48 | right: 0; 49 | width: 1px; 50 | height: 99%; 51 | background: linear-gradient(to bottom, #eee 0%, #ccc 50%, #eee 100%) fixed; 52 | } 53 | 54 | .card { 55 | position: relative; 56 | z-index: 1; 57 | background: #fff; 58 | width: 100%; 59 | padding: 10px 10px 10px 15px; 60 | margin: 0 0 10px 0; 61 | overflow: auto; 62 | border: 1px solid #e5e5df; 63 | border-radius: 3px; 64 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); 65 | } 66 | 67 | .card__title { 68 | font-weight: bold; 69 | border-bottom: solid 5px transparent; 70 | } 71 | 72 | .card__title:before { 73 | display: inline-block; 74 | width: 1em; 75 | content: '▸'; 76 | } 77 | 78 | .card__title--is-open:before { 79 | content: '▾'; 80 | } 81 | 82 | .checklist__task:first-child { 83 | margin-top: 10px; 84 | padding-top: 10px; 85 | border-top: dashed 1px #ddd; 86 | } 87 | 88 | .checklist__task--remove:after{ 89 | display: inline-block; 90 | color: #d66; 91 | content: "✖"; 92 | } 93 | -------------------------------------------------------------------------------- /kanban-app/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | /* 4 | * Default webpack configuration for development 5 | */ 6 | var config = { 7 | devtool: 'eval-source-map', 8 | entry: __dirname + "/app/App.js", 9 | output: { 10 | path: __dirname + "/public", 11 | filename: "bundle.js" 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: 'babel', 18 | query: { 19 | presets: ['es2015','react'] 20 | } 21 | }] 22 | }, 23 | devServer: { 24 | contentBase: "./public", 25 | colors: true, 26 | historyApiFallback: true, 27 | inline: true 28 | }, 29 | } 30 | 31 | /* 32 | * If bundling for production, optimize output 33 | */ 34 | if (process.env.NODE_ENV === 'production') { 35 | config.devtool = false; 36 | config.plugins = [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({comments: false}), 39 | new webpack.DefinePlugin({ 40 | 'process.env': {NODE_ENV: JSON.stringify('production')} 41 | }) 42 | ]; 43 | }; 44 | 45 | module.exports = config; 46 | --------------------------------------------------------------------------------