├── .gitattributes ├── Chapter01 ├── error-handling │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── MyError.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ └── registerServiceWorker.js ├── multiple-elements-and-strings │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── Multi.js │ │ ├── MultiWithString.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ └── registerServiceWorker.js └── portals │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── MyPortal.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── registerServiceWorker.js ├── Chapter02 └── snapterest │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ ├── source │ ├── app.js │ └── components │ │ └── .empty │ └── webpack.config.js ├── Chapter03 └── snapterest │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ ├── source │ ├── app.js │ └── components │ │ └── .empty │ └── webpack.config.js ├── Chapter04 └── snapterest │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ ├── source │ ├── app.js │ └── components │ │ ├── .empty │ │ ├── StatefulComponent.js │ │ └── StatelessComponent.js │ └── webpack.config.js ├── Chapter05 └── snapterest │ ├── gulpfile.js │ ├── package.json │ └── source │ ├── app.js │ └── components │ └── Application.js ├── Chapter06 └── snapterest │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ └── source │ ├── app.js │ └── components │ ├── Application.js │ ├── Stream.js │ └── StreamTweet.js ├── Chapter07 └── snapterest │ ├── gulpfile.js │ ├── package.json │ └── source │ ├── app.js │ └── components │ ├── Application.js │ ├── Collection.js │ ├── Header.js │ ├── Stream.js │ ├── StreamTweet.js │ └── Tweet.js ├── Chapter08 └── snapterest │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ ├── source │ ├── app.js │ ├── components │ │ ├── Application.js │ │ ├── Button.js │ │ ├── Collection.js │ │ ├── CollectionControls.js │ │ ├── CollectionExportForm.js │ │ ├── CollectionRenameForm.js │ │ ├── Header.js │ │ ├── Stream.js │ │ ├── StreamTweet.js │ │ ├── Tweet.js │ │ └── TweetList.js │ └── utils │ │ └── TweetUtils.js │ └── webpack.config.js ├── Chapter09 └── snapterest │ ├── .babelrc │ ├── enzyme-setup.js │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ └── source │ ├── app.js │ ├── components │ ├── Application.react.js │ ├── Button.js │ ├── Collection.react.js │ ├── CollectionControls.react.js │ ├── CollectionExportForm.react.js │ ├── CollectionRenameForm.react.js │ ├── Header.js │ ├── Stream.react.js │ ├── StreamTweet.react.js │ ├── Tweet.react.js │ ├── TweetList.react.js │ └── __tests__ │ │ ├── Button-test.js │ │ └── Header-test.js │ └── utils │ ├── CollectionUtils.js │ ├── TweetUtils.js │ └── __tests__ │ ├── CollectionUtils-test.js │ └── TweetUtils-test.js ├── Chapter10 └── snapterest │ ├── gulpfile.js │ ├── package.json │ └── source │ ├── actions │ └── TweetActionCreators.js │ ├── app.js │ ├── components │ ├── Application.react.js │ ├── Button.react.js │ ├── Collection.react.js │ ├── CollectionControls.react.js │ ├── CollectionExportForm.react.js │ ├── CollectionRenameForm.react.js │ ├── Header.react.js │ ├── Stream.react.js │ ├── StreamTweet.react.js │ ├── Tweet.react.js │ ├── TweetList.react.js │ └── __tests__ │ │ ├── Button-test.js │ │ └── Header-test.js │ ├── dispatcher │ └── AppDispatcher.js │ ├── stores │ └── TweetStore.js │ └── utils │ ├── CollectionUtils.js │ ├── TweetUtils.js │ └── __tests__ │ ├── CollectionUtils-test.js │ └── TweetUtils-test.js ├── Chapter11 └── snapterest │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ ├── source │ ├── actions │ │ ├── CollectionActionCreators.js │ │ └── TweetActionCreators.js │ ├── app.js │ ├── components │ │ ├── Application.js │ │ ├── Button.js │ │ ├── Collection.js │ │ ├── CollectionControls.js │ │ ├── CollectionExportForm.js │ │ ├── CollectionRenameForm.js │ │ ├── Header.js │ │ ├── Stream.js │ │ ├── StreamTweet.js │ │ ├── Tweet.js │ │ ├── TweetList.js │ │ └── __tests__ │ │ │ ├── Button-test.js │ │ │ └── Header-test.js │ ├── dispatcher │ │ └── AppDispatcher.js │ ├── stores │ │ ├── CollectionStore.js │ │ └── TweetStore.js │ └── utils │ │ ├── CollectionUtils.js │ │ ├── TweetUtils.js │ │ ├── WebAPIUtils.js │ │ └── __tests__ │ │ ├── CollectionUtils-test.js │ │ └── TweetUtils-test.js │ └── webpack.config.js ├── Chapter12 └── snapterest │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ ├── source │ ├── actions │ │ └── index.js │ ├── app.js │ ├── components │ │ ├── Application.js │ │ ├── Button.js │ │ ├── Collection.js │ │ ├── CollectionControls.js │ │ ├── CollectionExportForm.js │ │ ├── CollectionRenameForm.js │ │ ├── Header.js │ │ ├── Stream.js │ │ ├── StreamTweet.js │ │ ├── Tweet.js │ │ ├── TweetList.js │ │ └── __tests__ │ │ │ ├── Button-test.js │ │ │ └── Header-test.js │ ├── dispatcher │ │ └── AppDispatcher.js │ ├── reducers │ │ ├── collection.js │ │ ├── index.js │ │ └── tweet.js │ ├── stores │ │ └── index.js │ └── utils │ │ ├── CollectionUtils.js │ │ ├── TweetUtils.js │ │ ├── WebAPIUtils.js │ │ └── __tests__ │ │ ├── CollectionUtils-test.js │ │ └── TweetUtils-test.js │ └── webpack.config.js ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /Chapter01/error-handling/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /Chapter01/error-handling/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "error-handling", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.0.0", 7 | "react-dom": "^16.0.0", 8 | "react-scripts": "1.0.14" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build", 13 | "test": "react-scripts test --env=jsdom", 14 | "eject": "react-scripts eject" 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter01/error-handling/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/React-16-Essentials-Second-Edition/39d286537d11c979c8046e790f7cbca7126c5a69/Chapter01/error-handling/public/favicon.ico -------------------------------------------------------------------------------- /Chapter01/error-handling/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Chapter01/error-handling/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter01/error-handling/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter01/error-handling/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import MyError from './MyError'; 5 | 6 | class App extends Component { 7 | state = {} 8 | 9 | componentDidCatch(err) { 10 | this.setState({ err: err.message }); 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 |
17 | logo 18 |

Welcome to React

19 |
20 |

21 | To get started, edit src/App.js and save to reload. 22 |

23 |

24 |
25 | ); 26 | } 27 | } 28 | 29 | export default App; 30 | -------------------------------------------------------------------------------- /Chapter01/error-handling/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter01/error-handling/src/MyError.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const MyError = (props) => { 4 | if (props.err) { 5 | return {props.err}; 6 | } 7 | 8 | throw new Error('epic fail'); 9 | }; 10 | 11 | export default MyError; 12 | -------------------------------------------------------------------------------- /Chapter01/error-handling/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter01/error-handling/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /Chapter01/error-handling/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter01/error-handling/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiple-elements-and-strings", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.0.0", 7 | "react-dom": "^16.0.0", 8 | "react-scripts": "1.0.14" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build", 13 | "test": "react-scripts test --env=jsdom", 14 | "eject": "react-scripts eject" 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/React-16-Essentials-Second-Edition/39d286537d11c979c8046e790f7cbca7126c5a69/Chapter01/multiple-elements-and-strings/public/favicon.ico -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import Multi from './Multi'; 5 | import MultiWithString from './MultiWithString'; 6 | 7 | class App extends Component { 8 | render() { 9 | return ( 10 |
11 |
12 | logo 13 |

Welcome to React

14 |
15 | 16 | 17 |
18 | ); 19 | } 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/Multi.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Multi = () => [ 4 | 'first sibling', 5 | 'second sibling' 6 | ].map((v, i) =>

{v}

); 7 | 8 | export default Multi; 9 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/MultiWithString.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Label = () => 'Name:'; 4 | 5 | const MultiWithString = () => [ 6 | 'first sibling', 7 | 'second sibling' 8 | ].map((v, i) =>

); 9 | 10 | export default MultiWithString; 11 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter01/multiple-elements-and-strings/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Chapter01/portals/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /Chapter01/portals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portals", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.0.0", 7 | "react-dom": "^16.0.0", 8 | "react-scripts": "1.0.14" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build", 13 | "test": "react-scripts test --env=jsdom", 14 | "eject": "react-scripts eject" 15 | } 16 | } -------------------------------------------------------------------------------- /Chapter01/portals/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/React-16-Essentials-Second-Edition/39d286537d11c979c8046e790f7cbca7126c5a69/Chapter01/portals/public/favicon.ico -------------------------------------------------------------------------------- /Chapter01/portals/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Chapter01/portals/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter01/portals/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter01/portals/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import MyPortal from './MyPortal'; 5 | 6 | class App extends Component { 7 | render() { 8 | return ( 9 |
10 |
11 | logo 12 |

Welcome to React

13 |
14 |

15 | To get started, edit src/App.js and save to reload. 16 |

17 | Bro, you just notified me! 18 |
19 | ); 20 | } 21 | } 22 | 23 | export default App; 24 | -------------------------------------------------------------------------------- /Chapter01/portals/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter01/portals/src/MyPortal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { createPortal } from 'react-dom'; 3 | 4 | class MyPortal extends Component { 5 | constructor(...args) { 6 | super(...args); 7 | this.el = document.createElement('strong'); 8 | } 9 | 10 | componentWillMount() { 11 | document.body.appendChild(this.el); 12 | } 13 | 14 | componentWillUnmount() { 15 | document.body.removeChild(this.el); 16 | } 17 | 18 | render() { 19 | return createPortal( 20 | this.props.children, 21 | this.el 22 | ); 23 | } 24 | }; 25 | 26 | export default MyPortal; 27 | -------------------------------------------------------------------------------- /Chapter01/portals/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter01/portals/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /Chapter01/portals/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter01/portals/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Chapter02/snapterest/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('browserify'); 3 | var babelify = require('babelify'); 4 | var source = require('vinyl-source-stream'); 5 | 6 | gulp.task('default', function () { 7 | return browserify('./source/app.js') 8 | .transform(babelify) 9 | .bundle() 10 | .pipe(source('snapterest.js')) 11 | .pipe(gulp.dest('./build/')); 12 | }); -------------------------------------------------------------------------------- /Chapter02/snapterest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snapterest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack -p --config webpack.config.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "babel-core": "^6.26.0", 14 | "babel-loader": "^7.1.2", 15 | "babel-plugin-transform-class-properties": "^6.24.1", 16 | "babel-preset-latest": "^6.24.1", 17 | "babel-preset-react": "^6.24.1", 18 | "babelify": "^7.3.0", 19 | "browserify": "^14.4.0", 20 | "gulp": "^3.9.0", 21 | "vinyl-source-stream": "^1.1.0", 22 | "webpack": "^3.8.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Chapter02/snapterest/source/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/React-16-Essentials-Second-Edition/39d286537d11c979c8046e790f7cbca7126c5a69/Chapter02/snapterest/source/app.js -------------------------------------------------------------------------------- /Chapter02/snapterest/source/components/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/React-16-Essentials-Second-Edition/39d286537d11c979c8046e790f7cbca7126c5a69/Chapter02/snapterest/source/components/.empty -------------------------------------------------------------------------------- /Chapter02/snapterest/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './source/app.js', 5 | output: { 6 | path: path.resolve(__dirname, 'build'), 7 | filename: 'snapterest.js' 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.js$/, 13 | use: [ 14 | { 15 | loader: 'babel-loader', 16 | options: { 17 | presets: ['react', 'latest'], 18 | plugins: ['transform-class-properties'] 19 | } 20 | } 21 | ], 22 | exclude: path.resolve(__dirname, 'node_modules') 23 | } 24 | ] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /Chapter03/snapterest/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('browserify'); 3 | var babelify = require('babelify'); 4 | var source = require('vinyl-source-stream'); 5 | 6 | gulp.task('default', function () { 7 | return browserify('./source/app.js') 8 | .transform(babelify) 9 | .bundle() 10 | .pipe(source('snapterest.js')) 11 | .pipe(gulp.dest('./build/')); 12 | }); -------------------------------------------------------------------------------- /Chapter03/snapterest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snapterest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack -p --config webpack.config.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "babel-core": "^6.26.0", 14 | "babel-loader": "^7.1.2", 15 | "babel-plugin-transform-class-properties": "^6.24.1", 16 | "babel-preset-latest": "^6.24.1", 17 | "babel-preset-react": "^6.24.1", 18 | "babelify": "^7.3.0", 19 | "browserify": "^14.4.0", 20 | "gulp": "^3.9.0", 21 | "vinyl-source-stream": "^1.1.0", 22 | "webpack": "^3.8.1" 23 | }, 24 | "dependencies": { 25 | "react": "^16.0.0", 26 | "react-dom": "^16.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter03/snapterest/source/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | const listOfItems = ( 5 |
    6 |
  • Item 1
  • 7 |
  • Item 2
  • 8 |
  • Item 3
  • 9 |
10 | ); 11 | 12 | ReactDOM.render( 13 | listOfItems, 14 | document.getElementById('react-application') 15 | ); 16 | -------------------------------------------------------------------------------- /Chapter03/snapterest/source/components/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/React-16-Essentials-Second-Edition/39d286537d11c979c8046e790f7cbca7126c5a69/Chapter03/snapterest/source/components/.empty -------------------------------------------------------------------------------- /Chapter03/snapterest/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './source/app.js', 5 | output: { 6 | path: path.resolve(__dirname, 'build'), 7 | filename: 'snapterest.js' 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.js$/, 13 | use: [ 14 | { 15 | loader: 'babel-loader', 16 | options: { 17 | presets: ['react', 'latest'], 18 | plugins: ['transform-class-properties'] 19 | } 20 | } 21 | ], 22 | exclude: path.resolve(__dirname, 'node_modules') 23 | } 24 | ] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /Chapter04/snapterest/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('browserify'); 3 | var babelify = require('babelify'); 4 | var source = require('vinyl-source-stream'); 5 | 6 | gulp.task('default', function () { 7 | return browserify('./source/app.js') 8 | .transform(babelify) 9 | .bundle() 10 | .pipe(source('snapterest.js')) 11 | .pipe(gulp.dest('./build/')); 12 | }); -------------------------------------------------------------------------------- /Chapter04/snapterest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snapterest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack -p --config webpack.config.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "babel-core": "^6.26.0", 14 | "babel-loader": "^7.1.2", 15 | "babel-plugin-transform-class-properties": "^6.24.1", 16 | "babel-preset-latest": "^6.24.1", 17 | "babel-preset-react": "^6.24.1", 18 | "babelify": "^7.3.0", 19 | "browserify": "^14.4.0", 20 | "gulp": "^3.9.0", 21 | "vinyl-source-stream": "^1.1.0", 22 | "webpack": "^3.8.1" 23 | }, 24 | "dependencies": { 25 | "react": "^16.0.0", 26 | "react-dom": "^16.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter04/snapterest/source/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import StatefulComponent from './components/StatefulComponent'; 4 | 5 | ReactDOM.render( 6 |
7 | 8 |
, 9 | document.getElementById('react-application') 10 | ); 11 | -------------------------------------------------------------------------------- /Chapter04/snapterest/source/components/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/React-16-Essentials-Second-Edition/39d286537d11c979c8046e790f7cbca7126c5a69/Chapter04/snapterest/source/components/.empty -------------------------------------------------------------------------------- /Chapter04/snapterest/source/components/StatefulComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | class StatefulComponent extends Component { 5 | state = { 6 | isHeaderHidden: false 7 | }; 8 | 9 | handleClick = () => { 10 | this.setState({ 11 | isHeaderHidden: !this.state.isHeaderHidden 12 | }); 13 | } 14 | 15 | render() { 16 | const { isHeaderHidden } = this.state; 17 | 18 | if (isHeaderHidden) { 19 | return ( 20 | 26 | ); 27 | } 28 | 29 | return ( 30 |
31 |

32 | Stateful React Component 33 |

, 34 | 41 |
42 | ); 43 | } 44 | } 45 | 46 | export default StatefulComponent; 47 | -------------------------------------------------------------------------------- /Chapter04/snapterest/source/components/StatelessComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | class StatelessComponent extends Component { 5 | render() { 6 | return (

Stateless React Component

); 7 | } 8 | }); 9 | 10 | ReactDOM.render( 11 | , 12 | document.getElementById('react-application') 13 | ); 14 | -------------------------------------------------------------------------------- /Chapter04/snapterest/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './source/app.js', 5 | output: { 6 | path: path.resolve(__dirname, 'build'), 7 | filename: 'snapterest.js' 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.js$/, 13 | use: [ 14 | { 15 | loader: 'babel-loader', 16 | options: { 17 | presets: ['react', 'latest'], 18 | plugins: ['transform-class-properties'] 19 | } 20 | } 21 | ], 22 | exclude: path.resolve(__dirname, 'node_modules') 23 | } 24 | ] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /Chapter05/snapterest/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('browserify'); 3 | var babelify = require('babelify'); 4 | var source = require('vinyl-source-stream'); 5 | 6 | gulp.task('default', function () { 7 | return browserify('./source/app.js') 8 | .transform(babelify) 9 | .bundle() 10 | .pipe(source('snapterest.js')) 11 | .pipe(gulp.dest('./build/')); 12 | }); -------------------------------------------------------------------------------- /Chapter05/snapterest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snapterest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babelify": "^6.2.0", 13 | "browserify": "^11.0.1", 14 | "gulp": "^3.9.0", 15 | "vinyl-source-stream": "^1.1.0" 16 | }, 17 | "dependencies": { 18 | "react": "^0.14.0-beta3", 19 | "react-dom": "^0.14.0-beta3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter05/snapterest/source/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Application from './components/Application'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('react-application') 9 | ); 10 | -------------------------------------------------------------------------------- /Chapter05/snapterest/source/components/Application.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Stream from './Stream'; 3 | import Collection from './Collection'; 4 | 5 | class Application extends Component { 6 | state = { 7 | collectionTweets: {} 8 | } 9 | 10 | addTweetToCollection = (tweet) => { 11 | const { collectionTweets } = this.state; 12 | 13 | collectionTweets[tweet.id] = tweet; 14 | 15 | this.setState({ 16 | collectionTweets: collectionTweets 17 | }); 18 | } 19 | 20 | removeTweetFromCollection = (tweet) => { 21 | const { collectionTweets } = this.state; 22 | 23 | delete collectionTweets[tweet.id]; 24 | 25 | this.setState({ 26 | collectionTweets: collectionTweets 27 | }); 28 | } 29 | 30 | removeAllTweetsFromCollection = () => { 31 | this.setState({ 32 | collectionTweets: {} 33 | }); 34 | } 35 | 36 | render() { 37 | const { 38 | addTweetToCollection, 39 | removeTweetFromCollection, 40 | removeAllTweetsFromCollection 41 | } = this; 42 | 43 | return ( 44 |
45 |
46 |
47 | 48 |
49 |
50 | 55 |
56 |
57 |
58 | ); 59 | } 60 | } 61 | 62 | export default Application; 63 | -------------------------------------------------------------------------------- /Chapter06/snapterest/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('browserify'); 3 | var babelify = require('babelify'); 4 | var source = require('vinyl-source-stream'); 5 | 6 | gulp.task('default', function () { 7 | return browserify('./source/app.js') 8 | .transform(babelify) 9 | .bundle() 10 | .pipe(source('snapterest.js')) 11 | .pipe(gulp.dest('./build/')); 12 | }); -------------------------------------------------------------------------------- /Chapter06/snapterest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snapterest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babelify": "^6.2.0", 13 | "browserify": "^11.0.1", 14 | "gulp": "^3.9.0", 15 | "vinyl-source-stream": "^1.1.0" 16 | }, 17 | "dependencies": { 18 | "react": "^16.0.0", 19 | "react-dom": "^16.0.0", 20 | "snapkite-stream-client": "^1.0.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Chapter06/snapterest/source/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Application from './components/Application'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('react-application') 9 | ); 10 | -------------------------------------------------------------------------------- /Chapter06/snapterest/source/components/Application.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Stream from './Stream'; 3 | import Collection from './Collection'; 4 | 5 | class Application extends Component { 6 | state = { 7 | collectionTweets: {} 8 | } 9 | 10 | addTweetToCollection = (tweet) => { 11 | const { collectionTweets } = this.state; 12 | 13 | collectionTweets[tweet.id] = tweet; 14 | 15 | this.setState({ 16 | collectionTweets: collectionTweets 17 | }); 18 | } 19 | 20 | removeTweetFromCollection = (tweet) => { 21 | const { collectionTweets } = this.state; 22 | 23 | delete collectionTweets[tweet.id]; 24 | 25 | this.setState({ 26 | collectionTweets: collectionTweets 27 | }); 28 | } 29 | 30 | removeAllTweetsFromCollection = () => { 31 | this.setState({ 32 | collectionTweets: {} 33 | }); 34 | } 35 | 36 | render() { 37 | const { 38 | addTweetToCollection, 39 | removeTweetFromCollection, 40 | removeAllTweetsFromCollection 41 | } = this; 42 | 43 | return ( 44 |
45 |
46 |
47 | 48 |
49 |
50 | 55 |
56 |
57 |
58 | ); 59 | } 60 | } 61 | 62 | export default Application; 63 | -------------------------------------------------------------------------------- /Chapter06/snapterest/source/components/Stream.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import SnapkiteStreamClient from 'snapkite-stream-client'; 3 | import StreamTweet from './StreamTweet'; 4 | import Header from './Header.react'; 5 | 6 | class Stream extends Component { 7 | state = { 8 | tweet: null 9 | } 10 | 11 | componentDidMount() { 12 | SnapkiteStreamClient.initializeStream(this.handleNewTweet); 13 | } 14 | 15 | componentWillUnmount() { 16 | SnapkiteStreamClient.destroyStream(); 17 | } 18 | 19 | handleNewTweet = (tweet) => { 20 | this.setState({ 21 | tweet: tweet 22 | }); 23 | } 24 | 25 | render() { 26 | const { tweet } = this.state; 27 | const { onAddTweetToCollection } = this.props; 28 | const headerText = 'Waiting for public photos from Twitter...'; 29 | 30 | if (tweet) { 31 | return ( 32 | 36 | ); 37 | } 38 | 39 | return ( 40 |
41 | ); 42 | } 43 | }); 44 | 45 | export default Stream; 46 | -------------------------------------------------------------------------------- /Chapter06/snapterest/source/components/StreamTweet.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Header from './Header'; 4 | import Tweet from './Tweet'; 5 | 6 | class StreamTweet extends Component { 7 | state = { 8 | numberOfCharactersIsIncreasing: null, 9 | headerText: null 10 | } 11 | 12 | componentWillMount() { 13 | console.log('[Snapterest] StreamTweet: 1. Running componentWillMount()'); 14 | 15 | this.setState({ 16 | numberOfCharactersIsIncreasing: true, 17 | headerText: 'Latest public photo from Twitter' 18 | }); 19 | 20 | window.snapterest = { 21 | numberOfReceivedTweets: 1, 22 | numberOfDisplayedTweets: 1 23 | }; 24 | } 25 | 26 | componentDidMount() { 27 | console.log('[Snapterest] StreamTweet: 2. Running componentDidMount()'); 28 | 29 | const componentDOMRepresentation = ReactDOM.findDOMNode(this); 30 | window.snapterest.headerHtml = componentDOMRepresentation.children[0].outerHTML; 31 | window.snapterest.tweetHtml = componentDOMRepresentation.children[1].outerHTML; 32 | } 33 | 34 | componentWillUnmount() { 35 | console.log('[Snapterest] StreamTweet: 8. Running componentWillUnmount()'); 36 | 37 | delete window.snapterest; 38 | } 39 | 40 | render() { 41 | console.log('[Snapterest] StreamTweet: Running render()'); 42 | 43 | return ( 44 |
45 |
46 | 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default StreamTweet; 56 | -------------------------------------------------------------------------------- /Chapter07/snapterest/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('browserify'); 3 | var babelify = require('babelify'); 4 | var source = require('vinyl-source-stream'); 5 | 6 | gulp.task('default', function () { 7 | return browserify('./source/app.js') 8 | .transform(babelify) 9 | .bundle() 10 | .pipe(source('snapterest.js')) 11 | .pipe(gulp.dest('./build/')); 12 | }); -------------------------------------------------------------------------------- /Chapter07/snapterest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snapterest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babelify": "^6.2.0", 13 | "browserify": "^11.0.1", 14 | "gulp": "^3.9.0", 15 | "vinyl-source-stream": "^1.1.0" 16 | }, 17 | "dependencies": { 18 | "react": "^0.14.0-beta3", 19 | "react-dom": "^0.14.0-beta3", 20 | "snapkite-stream-client": "^1.0.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Chapter07/snapterest/source/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Application from './components/Application'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('react-application') 9 | ); 10 | -------------------------------------------------------------------------------- /Chapter07/snapterest/source/components/Application.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Stream from './Stream'; 3 | import Collection from './Collection'; 4 | 5 | class Application extends Component { 6 | state = { 7 | collectionTweets: {} 8 | } 9 | 10 | addTweetToCollection = (tweet) => { 11 | const { collectionTweets } = this.state; 12 | 13 | collectionTweets[tweet.id] = tweet; 14 | 15 | this.setState({ 16 | collectionTweets: collectionTweets 17 | }); 18 | } 19 | 20 | removeTweetFromCollection = (tweet) => { 21 | const { collectionTweets } = this.state; 22 | 23 | delete collectionTweets[tweet.id]; 24 | 25 | this.setState({ 26 | collectionTweets: collectionTweets 27 | }); 28 | } 29 | 30 | removeAllTweetsFromCollection = () => { 31 | this.setState({ 32 | collectionTweets: {} 33 | }); 34 | } 35 | 36 | render() { 37 | const { 38 | addTweetToCollection, 39 | removeTweetFromCollection, 40 | removeAllTweetsFromCollection 41 | } = this; 42 | 43 | return ( 44 |
45 |
46 |
47 | 48 |
49 |
50 | 55 |
56 |
57 |
58 | ); 59 | } 60 | } 61 | 62 | export default Application; 63 | -------------------------------------------------------------------------------- /Chapter07/snapterest/source/components/Collection.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOMServer from 'react-dom/server'; 3 | import CollectionControls from './CollectionControls'; 4 | import TweetList from './TweetList'; 5 | import Header from './Header'; 6 | 7 | class Collection extends Component { 8 | 9 | createHtmlMarkupStringOfTweetList() { 10 | const htmlString = ReactDOMServer.renderToStaticMarkup( 11 | 12 | ); 13 | 14 | const htmlMarkup = { 15 | html: htmlString 16 | }; 17 | 18 | return JSON.stringify(htmlMarkup); 19 | } 20 | 21 | getListOfTweetIds = () => 22 | Object.keys(this.props.tweets) 23 | 24 | getNumberOfTweetsInCollection = () => 25 | this.getListOfTweetIds().length 26 | 27 | render() { 28 | const numberOfTweetsInCollection = this.getNumberOfTweetsInCollection(); 29 | 30 | if (numberOfTweetsInCollection > 0) { 31 | const htmlMarkup = this.createHtmlMarkupStringOfTweetList(); 32 | const tweets = this.props.tweets; 33 | const removeAllTweetsFromCollection = this.props.onRemoveAllTweetsFromCollection; 34 | const handleRemoveTweetFromCollection = this.props.onRemoveTweetFromCollection; 35 | 36 | return ( 37 |
38 | 43 | 47 |
48 | ); 49 | } 50 | 51 | return
; 52 | } 53 | }); 54 | 55 | export default Collection; 56 | -------------------------------------------------------------------------------- /Chapter07/snapterest/source/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const DEFAULT_HEADER_TEXT = 'Default header'; 4 | 5 | const headerStyle = { 6 | fontSize: '16px', 7 | fontWeight: '300', 8 | display: 'inline-block', 9 | margin: '20px 10px' 10 | }; 11 | 12 | class Header extends React.Component { 13 | 14 | render() { 15 | const { text } = this.props; 16 | 17 | return ( 18 |

{text}

19 | ); 20 | } 21 | } 22 | 23 | Header.defaultProps = { 24 | text: DEFAULT_HEADER_TEXT 25 | }; 26 | 27 | export default Header; 28 | -------------------------------------------------------------------------------- /Chapter07/snapterest/source/components/Stream.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import SnapkiteStreamClient from 'snapkite-stream-client'; 3 | import StreamTweet from './StreamTweet'; 4 | import Header from './Header.react'; 5 | 6 | class Stream extends Component { 7 | state = { 8 | tweet: null 9 | } 10 | 11 | componentDidMount() { 12 | SnapkiteStreamClient.initializeStream(this.handleNewTweet); 13 | } 14 | 15 | componentWillUnmount() { 16 | SnapkiteStreamClient.destroyStream(); 17 | } 18 | 19 | handleNewTweet = (tweet) => { 20 | this.setState({ 21 | tweet: tweet 22 | }); 23 | } 24 | 25 | render() { 26 | const { tweet } = this.state; 27 | const { onAddTweetToCollection } = this.props; 28 | const headerText = 'Waiting for public photos from Twitter...'; 29 | 30 | if (tweet) { 31 | return ( 32 | 36 | ); 37 | } 38 | 39 | return ( 40 |
41 | ); 42 | } 43 | }); 44 | 45 | export default Stream; 46 | -------------------------------------------------------------------------------- /Chapter07/snapterest/source/components/StreamTweet.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Header from './Header'; 4 | import Tweet from './Tweet'; 5 | 6 | class StreamTweet extends Component { 7 | state = { 8 | numberOfCharactersIsIncreasing: null, 9 | headerText: null 10 | } 11 | 12 | componentWillMount() { 13 | console.log('[Snapterest] StreamTweet: 1. Running componentWillMount()'); 14 | 15 | this.setState({ 16 | numberOfCharactersIsIncreasing: true, 17 | headerText: 'Latest public photo from Twitter' 18 | }); 19 | 20 | window.snapterest = { 21 | numberOfReceivedTweets: 1, 22 | numberOfDisplayedTweets: 1 23 | }; 24 | } 25 | 26 | componentDidMount() { 27 | console.log('[Snapterest] StreamTweet: 2. Running componentDidMount()'); 28 | 29 | const componentDOMRepresentation = ReactDOM.findDOMNode(this); 30 | window.snapterest.headerHtml = componentDOMRepresentation.children[0].outerHTML; 31 | window.snapterest.tweetHtml = componentDOMRepresentation.children[1].outerHTML; 32 | } 33 | 34 | componentWillReceiveProps(nextProps) { 35 | console.log('[Snapterest] StreamTweet: 4. Running componentWillReceiveProps()'); 36 | 37 | const { tweet: currentTweet } = this.props; 38 | const { tweet: nextTweet } = nextProps; 39 | 40 | const currentTweetLength = currentTweet.text.length; 41 | const nextTweetLength = nextTweet.text.length; 42 | const isNumberOfCharactersIncreasing = (nextTweetLength > currentTweetLength); 43 | let headerText; 44 | 45 | this.setState({ 46 | numberOfCharactersIsIncreasing: isNumberOfCharactersIncreasing 47 | }); 48 | 49 | if (isNumberOfCharactersIncreasing) { 50 | headerText = 'Number of characters is increasing'; 51 | } else { 52 | headerText = 'Latest public photo from Twitter'; 53 | } 54 | 55 | this.setState({ 56 | headerText 57 | }); 58 | 59 | window.snapterest.numberOfReceivedTweets++; 60 | } 61 | 62 | shouldComponentUpdate(nextProps, nextState) { 63 | console.log('[Snapterest] StreamTweet: 5. Running shouldComponentUpdate()'); 64 | return (nextProps.tweet.text.length > 1); 65 | } 66 | 67 | componentWillUpdate(nextProps, nextState) { 68 | console.log('[Snapterest] StreamTweet: 6. Running componentWillUpdate()'); 69 | } 70 | 71 | componentDidUpdate(prevProps, prevState) { 72 | console.log('[Snapterest] StreamTweet: 7. Running componentDidUpdate()'); 73 | window.snapterest.numberOfDisplayedTweets++; 74 | } 75 | 76 | componentWillUnmount() { 77 | console.log('[Snapterest] StreamTweet: 8. Running componentWillUnmount()'); 78 | 79 | delete window.snapterest; 80 | } 81 | 82 | render() { 83 | console.log('[Snapterest] StreamTweet: Running render()'); 84 | 85 | return ( 86 |
87 |
88 | 92 |
93 | ); 94 | } 95 | } 96 | 97 | export default StreamTweet; 98 | -------------------------------------------------------------------------------- /Chapter07/snapterest/source/components/Tweet.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const tweetStyle = { 5 | position: 'relative', 6 | display: 'inline-block', 7 | width: '300px', 8 | height: '400px', 9 | margin: '10px' 10 | }; 11 | 12 | const imageStyle = { 13 | maxHeight: '400px', 14 | boxShadow: '0px 1px 1px 0px #aaa', 15 | border: '1px solid #fff' 16 | }; 17 | 18 | class Tweet extends Component { 19 | static propTypes = { 20 | tweet(properties, propertyName, componentName) { 21 | const tweet = properties[propertyName]; 22 | 23 | if (!tweet) { 24 | return new Error('Tweet must be set.'); 25 | } 26 | 27 | if (!tweet.media) { 28 | return new Error('Tweet must have an image.'); 29 | } 30 | }, 31 | onImageClick: PropTypes.func 32 | } 33 | 34 | handleImageClick = () => { 35 | const { 36 | tweet, 37 | onImageClick 38 | } = this.props; 39 | 40 | if (onImageClick) { 41 | onImageClick(tweet); 42 | } 43 | } 44 | 45 | render() { 46 | const tweet = this.props.tweet; 47 | const tweetMediaUrl = tweet.media[0].url; 48 | 49 | return ( 50 |
51 | 56 |
57 | ); 58 | } 59 | } 60 | 61 | export default Tweet; 62 | -------------------------------------------------------------------------------- /Chapter08/snapterest/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('browserify'); 3 | var babelify = require('babelify'); 4 | var source = require('vinyl-source-stream'); 5 | 6 | gulp.task('default', function () { 7 | return browserify('./source/app.js') 8 | .transform(babelify) 9 | .bundle() 10 | .pipe(source('snapterest.js')) 11 | .pipe(gulp.dest('./build/')); 12 | }); -------------------------------------------------------------------------------- /Chapter08/snapterest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snapterest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack -p --config webpack.config.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "babel-core": "^6.26.0", 14 | "babel-loader": "^7.1.2", 15 | "babel-plugin-transform-class-properties": "^6.24.1", 16 | "babel-preset-latest": "^6.24.1", 17 | "babel-preset-react": "^6.24.1", 18 | "babelify": "^7.3.0", 19 | "browserify": "^14.4.0", 20 | "gulp": "^3.9.0", 21 | "vinyl-source-stream": "^1.1.0", 22 | "webpack": "^3.8.1" 23 | }, 24 | "dependencies": { 25 | "react": "^16.0.0", 26 | "react-dom": "^16.0.0", 27 | "snapkite-stream-client": "^1.0.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Chapter08/snapterest/source/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Application from './components/Application'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('react-application') 9 | ); 10 | -------------------------------------------------------------------------------- /Chapter08/snapterest/source/components/Application.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Stream from './Stream'; 3 | import Collection from './Collection'; 4 | 5 | class Application extends Component { 6 | state = { 7 | collectionTweets: {} 8 | } 9 | 10 | addTweetToCollection = (tweet) => { 11 | const { collectionTweets } = this.state; 12 | 13 | collectionTweets[tweet.id] = tweet; 14 | 15 | this.setState({ 16 | collectionTweets: collectionTweets 17 | }); 18 | } 19 | 20 | removeTweetFromCollection = (tweet) => { 21 | const { collectionTweets } = this.state; 22 | 23 | delete collectionTweets[tweet.id]; 24 | 25 | this.setState({ 26 | collectionTweets: collectionTweets 27 | }); 28 | } 29 | 30 | removeAllTweetsFromCollection = () => { 31 | this.setState({ 32 | collectionTweets: {} 33 | }); 34 | } 35 | 36 | render() { 37 | const { 38 | addTweetToCollection, 39 | removeTweetFromCollection, 40 | removeAllTweetsFromCollection 41 | } = this; 42 | 43 | return ( 44 |
45 |
46 |
47 | 48 |
49 |
50 | 55 |
56 |
57 |
58 | ); 59 | } 60 | } 61 | 62 | export default Application; 63 | -------------------------------------------------------------------------------- /Chapter08/snapterest/source/components/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const buttonStyle = { 4 | margin: '10px 10px 10px 0' 5 | }; 6 | 7 | const Button = ({ label, handleClick }) => ( 8 | 15 | ); 16 | 17 | export default Button; 18 | -------------------------------------------------------------------------------- /Chapter08/snapterest/source/components/Collection.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOMServer from 'react-dom/server'; 3 | import CollectionControls from './CollectionControls'; 4 | import TweetList from './TweetList'; 5 | import Header from './Header'; 6 | 7 | class Collection extends Component { 8 | 9 | createHtmlMarkupStringOfTweetList() { 10 | const htmlString = ReactDOMServer.renderToStaticMarkup( 11 | 12 | ); 13 | 14 | const htmlMarkup = { 15 | html: htmlString 16 | }; 17 | 18 | return JSON.stringify(htmlMarkup); 19 | } 20 | 21 | getListOfTweetIds = () => 22 | Object.keys(this.props.tweets) 23 | 24 | getNumberOfTweetsInCollection = () => 25 | this.getListOfTweetIds().length 26 | 27 | render() { 28 | const numberOfTweetsInCollection = this.getNumberOfTweetsInCollection(); 29 | 30 | if (numberOfTweetsInCollection > 0) { 31 | const htmlMarkup = this.createHtmlMarkupStringOfTweetList(); 32 | const tweets = this.props.tweets; 33 | const removeAllTweetsFromCollection = this.props.onRemoveAllTweetsFromCollection; 34 | const handleRemoveTweetFromCollection = this.props.onRemoveTweetFromCollection; 35 | 36 | return ( 37 |
38 | 43 | 47 |
48 | ); 49 | } 50 | 51 | return
; 52 | } 53 | } 54 | 55 | export default Collection; 56 | -------------------------------------------------------------------------------- /Chapter08/snapterest/source/components/CollectionControls.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import Header from './Header'; 4 | import Button from './Button'; 5 | import CollectionRenameForm from './CollectionRenameForm'; 6 | import CollectionExportForm from './CollectionExportForm'; 7 | 8 | class CollectionControls extends Component { 9 | state = { 10 | name: 'new', 11 | isEditingName: false 12 | } 13 | 14 | getHeaderText = () => { 15 | const { name } = this.state; 16 | const { numberOfTweetsInCollection } = this.props; 17 | let text = numberOfTweetsInCollection; 18 | 19 | if (numberOfTweetsInCollection === 1) { 20 | text = `${text} tweet in your`; 21 | } else { 22 | text = `${text} tweets in your`; 23 | } 24 | 25 | return ( 26 | 27 | {text} {name} collection 28 | 29 | ); 30 | } 31 | 32 | toggleEditCollectionName = () => { 33 | this.setState(prevState => ({ 34 | isEditingName: !prevState.isEditingName 35 | })); 36 | } 37 | 38 | setCollectionName = (name) => { 39 | this.setState({ 40 | name: name, 41 | isEditingName: false 42 | }); 43 | } 44 | 45 | render() { 46 | const { name, isEditingName } = this.state; 47 | const { 48 | onRemoveAllTweetsFromCollection, 49 | htmlMarkup 50 | } = this.props; 51 | 52 | if (isEditingName) { 53 | return ( 54 | 59 | ); 60 | } 61 | 62 | return ( 63 |
64 |
65 | 66 |
78 | ); 79 | } 80 | } 81 | 82 | export default CollectionControls; 83 | -------------------------------------------------------------------------------- /Chapter08/snapterest/source/components/CollectionExportForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const formStyle = { 4 | display: 'inline-block' 5 | }; 6 | 7 | const CollectionExportForm = ({ htmlMarkup }) => ( 8 |
12 | 13 | 14 | 17 |
18 | ); 19 | 20 | export default CollectionExportForm; 21 | -------------------------------------------------------------------------------- /Chapter08/snapterest/source/components/CollectionRenameForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Header from './Header'; 3 | import Button from './Button'; 4 | 5 | const inputStyle = { 6 | marginRight: '5px' 7 | }; 8 | 9 | class CollectionRenameForm extends Component { 10 | constructor(props) { 11 | super(props); 12 | 13 | const { name } = props; 14 | 15 | this.state = { 16 | inputValue: name 17 | }; 18 | } 19 | 20 | setInputValue = (inputValue) => { 21 | this.setState({ 22 | inputValue: inputValue 23 | }); 24 | } 25 | 26 | handleInputValueChange = (event) => { 27 | const inputValue = event.target.value; 28 | this.setInputValue(inputValue); 29 | } 30 | 31 | handleFormSubmit = (event) => { 32 | event.preventDefault(); 33 | 34 | const { onChangeCollectionName } = this.props; 35 | const { inputValue: collectionName } = this.state; 36 | 37 | onChangeCollectionName(collectionName); 38 | } 39 | 40 | handleFormCancel = (event) => { 41 | event.preventDefault(); 42 | 43 | const { 44 | name: collectionName, 45 | onCancelCollectionNameChange 46 | } = this.props; 47 | 48 | this.setInputValue(collectionName); 49 | onCancelCollectionNameChange(); 50 | } 51 | 52 | componentDidMount() { 53 | this.refs.collectionName.focus(); 54 | } 55 | 56 | render() { 57 | return ( 58 |
59 |
60 | 61 |
62 | 68 |
69 | 70 | 15 | ); 16 | 17 | export default Button; 18 | -------------------------------------------------------------------------------- /Chapter09/snapterest/source/components/Collection.react.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDOMServer = require('react-dom/server'); 3 | var CollectionControls = require('./CollectionControls.react'); 4 | var TweetList = require('./TweetList.react'); 5 | var Header = require('./Header.react'); 6 | 7 | var Collection = React.createClass({ 8 | 9 | createHtmlMarkupStringOfTweetList: function () { 10 | var htmlString = ReactDOMServer.renderToStaticMarkup( 11 | 12 | ); 13 | 14 | var htmlMarkup = { 15 | html: htmlString 16 | }; 17 | 18 | return JSON.stringify(htmlMarkup); 19 | }, 20 | 21 | getListOfTweetIds: function () { 22 | return Object.keys(this.props.tweets); 23 | }, 24 | 25 | getNumberOfTweetsInCollection: function () { 26 | return this.getListOfTweetIds().length; 27 | }, 28 | 29 | render: function () { 30 | var numberOfTweetsInCollection = this.getNumberOfTweetsInCollection(); 31 | 32 | if (numberOfTweetsInCollection > 0) { 33 | var tweets = this.props.tweets; 34 | var htmlMarkup = this.createHtmlMarkupStringOfTweetList(); 35 | var removeAllTweetsFromCollection = this.props.onRemoveAllTweetsFromCollection; 36 | var handleRemoveTweetFromCollection = this.props.onRemoveTweetFromCollection; 37 | 38 | return ( 39 |
40 | 44 | 45 | 48 |
49 | ); 50 | } 51 | 52 | return
; 53 | } 54 | }); 55 | 56 | module.exports = Collection; -------------------------------------------------------------------------------- /Chapter09/snapterest/source/components/CollectionControls.react.js: -------------------------------------------------------------------------------- 1 | 2 | var React = require('react'); 3 | var Header = require('./Header.react'); 4 | var Button = require('./Button.react'); 5 | var CollectionRenameForm = require('./CollectionRenameForm.react'); 6 | var CollectionExportForm = require('./CollectionExportForm.react'); 7 | 8 | var CollectionControls = React.createClass({ 9 | 10 | getInitialState: function () { 11 | return { 12 | name: 'new', 13 | isEditingName: false 14 | }; 15 | }, 16 | 17 | getHeaderText: function () { 18 | var numberOfTweetsInCollection = this.props.numberOfTweetsInCollection; 19 | var text = numberOfTweetsInCollection; 20 | 21 | if (numberOfTweetsInCollection === 1) { 22 | text = text + ' tweet in your'; 23 | } else { 24 | text = text + ' tweets in your'; 25 | } 26 | 27 | return ( 28 | 29 | {text} {this.state.name} collection 30 | 31 | ); 32 | }, 33 | 34 | toggleEditCollectionName: function () { 35 | this.setState({ 36 | isEditingName: !this.state.isEditingName 37 | }); 38 | }, 39 | 40 | setCollectionName: function (name) { 41 | this.setState({ 42 | name: name, 43 | isEditingName: false 44 | }); 45 | }, 46 | 47 | render: function () { 48 | if (this.state.isEditingName) { 49 | return ( 50 | 54 | ); 55 | } 56 | 57 | return ( 58 |
59 |
60 | 61 |
71 | ); 72 | } 73 | }); 74 | 75 | module.exports = CollectionControls; 76 | -------------------------------------------------------------------------------- /Chapter09/snapterest/source/components/CollectionExportForm.react.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var formStyle = { 4 | display: 'inline-block' 5 | }; 6 | 7 | var CollectionExportForm = React.createClass({ 8 | render: function () { 9 | return ( 10 |
11 | 12 | 13 | 14 |
15 | ); 16 | } 17 | }); 18 | 19 | module.exports = CollectionExportForm; -------------------------------------------------------------------------------- /Chapter09/snapterest/source/components/CollectionRenameForm.react.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Header = require('./Header.react'); 3 | var Button = require('./Button.react'); 4 | 5 | var inputStyle = { 6 | marginRight: '5px' 7 | }; 8 | 9 | var CollectionRenameForm = React.createClass({ 10 | 11 | getInitialState: function() { 12 | return { 13 | inputValue: this.props.name 14 | }; 15 | }, 16 | 17 | setInputValue: function (inputValue) { 18 | this.setState({ 19 | inputValue: inputValue 20 | }); 21 | }, 22 | 23 | handleInputValueChange: function (event) { 24 | var inputValue = event.target.value; 25 | this.setInputValue(inputValue); 26 | }, 27 | 28 | handleFormSubmit: function (event) { 29 | event.preventDefault(); 30 | var collectionName = this.state.inputValue; 31 | this.props.onChangeCollectionName(collectionName); 32 | }, 33 | 34 | handleFormCancel: function (event) { 35 | event.preventDefault(); 36 | var collectionName = this.props.name; 37 | this.setInputValue(collectionName); 38 | this.props.onCancelCollectionNameChange(); 39 | }, 40 | 41 | componentDidMount: function () { 42 | this.refs.collectionName.focus(); 43 | }, 44 | 45 | render: function () { 46 | return ( 47 |
48 |
49 | 50 |
51 | 57 |
58 | 59 | 14 | ); 15 | } 16 | }); 17 | 18 | module.exports = Button; -------------------------------------------------------------------------------- /Chapter10/snapterest/source/components/Collection.react.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDOMServer = require('react-dom/server'); 3 | var CollectionControls = require('./CollectionControls.react'); 4 | var TweetList = require('./TweetList.react'); 5 | var Header = require('./Header.react'); 6 | 7 | var Collection = React.createClass({ 8 | 9 | createHtmlMarkupStringOfTweetList: function () { 10 | var htmlString = ReactDOMServer.renderToStaticMarkup( 11 | 12 | ); 13 | 14 | var htmlMarkup = { 15 | html: htmlString 16 | }; 17 | 18 | return JSON.stringify(htmlMarkup); 19 | }, 20 | 21 | getListOfTweetIds: function () { 22 | return Object.keys(this.props.tweets); 23 | }, 24 | 25 | getNumberOfTweetsInCollection: function () { 26 | return this.getListOfTweetIds().length; 27 | }, 28 | 29 | render: function () { 30 | var numberOfTweetsInCollection = this.getNumberOfTweetsInCollection(); 31 | 32 | if (numberOfTweetsInCollection > 0) { 33 | var tweets = this.props.tweets; 34 | var htmlMarkup = this.createHtmlMarkupStringOfTweetList(); 35 | var removeAllTweetsFromCollection = this.props.onRemoveAllTweetsFromCollection; 36 | var handleRemoveTweetFromCollection = this.props.onRemoveTweetFromCollection; 37 | 38 | return ( 39 |
40 | 44 | 45 | 48 |
49 | ); 50 | } 51 | 52 | return
; 53 | } 54 | }); 55 | 56 | module.exports = Collection; -------------------------------------------------------------------------------- /Chapter10/snapterest/source/components/CollectionControls.react.js: -------------------------------------------------------------------------------- 1 | 2 | var React = require('react'); 3 | var Header = require('./Header.react'); 4 | var Button = require('./Button.react'); 5 | var CollectionRenameForm = require('./CollectionRenameForm.react'); 6 | var CollectionExportForm = require('./CollectionExportForm.react'); 7 | 8 | var CollectionControls = React.createClass({ 9 | 10 | getInitialState: function () { 11 | return { 12 | name: 'new', 13 | isEditingName: false 14 | }; 15 | }, 16 | 17 | getHeaderText: function () { 18 | var numberOfTweetsInCollection = this.props.numberOfTweetsInCollection; 19 | var text = numberOfTweetsInCollection; 20 | 21 | if (numberOfTweetsInCollection === 1) { 22 | text = text + ' tweet in your'; 23 | } else { 24 | text = text + ' tweets in your'; 25 | } 26 | 27 | return ( 28 | 29 | {text} {this.state.name} collection 30 | 31 | ); 32 | }, 33 | 34 | toggleEditCollectionName: function () { 35 | this.setState({ 36 | isEditingName: !this.state.isEditingName 37 | }); 38 | }, 39 | 40 | setCollectionName: function (name) { 41 | this.setState({ 42 | name: name, 43 | isEditingName: false 44 | }); 45 | }, 46 | 47 | render: function () { 48 | if (this.state.isEditingName) { 49 | return ( 50 | 54 | ); 55 | } 56 | 57 | return ( 58 |
59 |
60 | 61 |
71 | ); 72 | } 73 | }); 74 | 75 | module.exports = CollectionControls; 76 | -------------------------------------------------------------------------------- /Chapter10/snapterest/source/components/CollectionExportForm.react.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var formStyle = { 4 | display: 'inline-block' 5 | }; 6 | 7 | var CollectionExportForm = React.createClass({ 8 | render: function () { 9 | return ( 10 |
11 | 12 | 13 | 14 |
15 | ); 16 | } 17 | }); 18 | 19 | module.exports = CollectionExportForm; -------------------------------------------------------------------------------- /Chapter10/snapterest/source/components/CollectionRenameForm.react.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Header = require('./Header.react'); 3 | var Button = require('./Button.react'); 4 | 5 | var inputStyle = { 6 | marginRight: '5px' 7 | }; 8 | 9 | var CollectionRenameForm = React.createClass({ 10 | 11 | getInitialState: function() { 12 | return { 13 | inputValue: this.props.name 14 | }; 15 | }, 16 | 17 | setInputValue: function (inputValue) { 18 | this.setState({ 19 | inputValue: inputValue 20 | }); 21 | }, 22 | 23 | handleInputValueChange: function (event) { 24 | var inputValue = event.target.value; 25 | this.setInputValue(inputValue); 26 | }, 27 | 28 | handleFormSubmit: function (event) { 29 | event.preventDefault(); 30 | 31 | var collectionName = this.state.inputValue; 32 | this.props.onChangeCollectionName(collectionName); 33 | }, 34 | 35 | handleFormCancel: function (event) { 36 | event.preventDefault(); 37 | 38 | var collectionName = this.props.name; 39 | this.setInputValue(collectionName); 40 | this.props.onCancelCollectionNameChange(); 41 | }, 42 | 43 | componentDidMount: function () { 44 | this.refs.collectionName.focus(); 45 | }, 46 | 47 | render: function () { 48 | return ( 49 |
50 |
51 | 52 |
53 | 59 |
60 | 61 | 15 | ); 16 | 17 | export default Button; 18 | -------------------------------------------------------------------------------- /Chapter11/snapterest/source/components/Collection.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOMServer from 'react-dom/server'; 3 | import CollectionControls from './CollectionControls'; 4 | import TweetList from './TweetList'; 5 | import Header from './Header'; 6 | import CollectionUtils from '../utils/CollectionUtils'; 7 | import CollectionStore from '../stores/CollectionStore'; 8 | 9 | class Collection extends Component { 10 | state = { 11 | collectionTweets: CollectionStore.getCollectionTweets() 12 | } 13 | 14 | componentDidMount() { 15 | CollectionStore.addChangeListener(this.onCollectionChange); 16 | } 17 | 18 | componentWillUnmount() { 19 | CollectionStore.removeChangeListener(this.onCollectionChange); 20 | } 21 | 22 | onCollectionChange = () => { 23 | this.setState({ 24 | collectionTweets: CollectionStore.getCollectionTweets() 25 | }); 26 | } 27 | 28 | createHtmlMarkupStringOfTweetList() { 29 | const htmlString = ReactDOMServer.renderToStaticMarkup( 30 | 31 | ); 32 | 33 | const htmlMarkup = { 34 | html: htmlString 35 | }; 36 | 37 | return JSON.stringify(htmlMarkup); 38 | } 39 | 40 | render() { 41 | const { collectionTweets } = this.state; 42 | const numberOfTweetsInCollection = CollectionUtils 43 | .getNumberOfTweetsInCollection(collectionTweets); 44 | let htmlMarkup; 45 | 46 | if (numberOfTweetsInCollection > 0) { 47 | htmlMarkup = this.createHtmlMarkupStringOfTweetList(); 48 | 49 | return ( 50 |
51 | 55 | 56 | 57 |
58 | ); 59 | } 60 | 61 | return (
); 62 | } 63 | } 64 | 65 | export default Collection; 66 | -------------------------------------------------------------------------------- /Chapter11/snapterest/source/components/CollectionControls.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Header from './Header'; 3 | import Button from './Button'; 4 | import CollectionRenameForm from './CollectionRenameForm'; 5 | import CollectionExportForm from './CollectionExportForm'; 6 | import CollectionActionCreators from '../actions/CollectionActionCreators'; 7 | import CollectionStore from '../stores/CollectionStore'; 8 | 9 | class CollectionControls extends Component { 10 | state = { 11 | isEditingName: false 12 | } 13 | 14 | getHeaderText = () => { 15 | const { numberOfTweetsInCollection } = this.props; 16 | let text = numberOfTweetsInCollection; 17 | const name = CollectionStore.getCollectionName(); 18 | 19 | if (numberOfTweetsInCollection === 1) { 20 | text = `${text} tweet in your`; 21 | } else { 22 | text = `${text} tweets in your`; 23 | } 24 | 25 | return ( 26 | 27 | {text} {name} collection 28 | 29 | ); 30 | } 31 | 32 | toggleEditCollectionName = () => { 33 | this.setState(prevState => ({ 34 | isEditingName: !prevState.isEditingName 35 | })); 36 | } 37 | 38 | removeAllTweetsFromCollection = () => { 39 | CollectionActionCreators.removeAllTweetsFromCollection(); 40 | } 41 | 42 | render() { 43 | const { name, isEditingName } = this.state; 44 | const onRemoveAllTweetsFromCollection = this.removeAllTweetsFromCollection; 45 | const { htmlMarkup } = this.props; 46 | 47 | if (isEditingName) { 48 | return ( 49 | 53 | ); 54 | } 55 | 56 | return ( 57 |
58 |
59 | 60 |
72 | ); 73 | } 74 | } 75 | 76 | export default CollectionControls; 77 | -------------------------------------------------------------------------------- /Chapter11/snapterest/source/components/CollectionExportForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const formStyle = { 4 | display: 'inline-block' 5 | }; 6 | 7 | const CollectionExportForm = ({ htmlMarkup }) => ( 8 |
12 | 13 | 14 | 17 |
18 | ); 19 | 20 | export default CollectionExportForm; 21 | -------------------------------------------------------------------------------- /Chapter11/snapterest/source/components/CollectionRenameForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Header from './Header'; 3 | import Button from './Button'; 4 | import CollectionActionCreators from '../actions/CollectionActionCreators'; 5 | import CollectionStore from '../stores/CollectionStore'; 6 | 7 | const inputStyle = { 8 | marginRight: '5px' 9 | }; 10 | 11 | class CollectionRenameForm extends Component { 12 | state = { 13 | inputValue: name 14 | } 15 | 16 | setInputValue = (inputValue) => { 17 | this.setState({ 18 | inputValue 19 | }); 20 | } 21 | 22 | handleInputValueChange = (event) => { 23 | const inputValue = event.target.value; 24 | this.setInputValue(inputValue); 25 | } 26 | 27 | handleFormSubmit = (event) => { 28 | event.preventDefault(); 29 | 30 | const { onCancelCollectionNameChange } = this.props; 31 | const { inputValue: collectionName } = this.state; 32 | 33 | CollectionActionCreators.setCollectionName(collectionName); 34 | onCancelCollectionNameChange(); 35 | } 36 | 37 | handleFormCancel = (event) => { 38 | event.preventDefault(); 39 | 40 | const { onCancelCollectionNameChange } = this.props; 41 | const collectionName = CollectionStore.getCollectionName(); 42 | 43 | this.setInputValue(collectionName); 44 | onCancelCollectionNameChange(); 45 | } 46 | 47 | componentDidMount() { 48 | this.refs.collectionName.focus(); 49 | } 50 | 51 | render() { 52 | return ( 53 |
54 |
55 | 56 |
57 | 63 |
64 | 65 | 15 | ); 16 | 17 | export default Button; 18 | -------------------------------------------------------------------------------- /Chapter12/snapterest/source/components/Collection.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOMServer from 'react-dom/server'; 3 | import { connect } from 'react-redux'; 4 | 5 | import CollectionControls from './CollectionControls'; 6 | import TweetList from './TweetList'; 7 | import Header from './Header'; 8 | import CollectionUtils from '../utils/CollectionUtils'; 9 | 10 | class Collection extends Component { 11 | createHtmlMarkupStringOfTweetList() { 12 | const { collectionTweets } = this.props; 13 | const htmlString = ReactDOMServer.renderToStaticMarkup( 14 | 15 | ); 16 | 17 | const htmlMarkup = { 18 | html: htmlString 19 | }; 20 | 21 | return JSON.stringify(htmlMarkup); 22 | } 23 | 24 | render() { 25 | const { collectionTweets } = this.props; 26 | const numberOfTweetsInCollection = CollectionUtils 27 | .getNumberOfTweetsInCollection(collectionTweets); 28 | let htmlMarkup; 29 | 30 | if (numberOfTweetsInCollection > 0) { 31 | htmlMarkup = this.createHtmlMarkupStringOfTweetList(); 32 | 33 | return ( 34 |
35 | 39 | 40 | 41 |
42 | ); 43 | } 44 | 45 | return (
); 46 | } 47 | } 48 | 49 | const mapStateToProps = state => state.collection; 50 | 51 | const mapDispatchToProps = dispatch => ({}); 52 | 53 | export default connect( 54 | mapStateToProps, 55 | mapDispatchToProps 56 | )(Collection); 57 | -------------------------------------------------------------------------------- /Chapter12/snapterest/source/components/CollectionControls.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Header from './Header'; 5 | import Button from './Button'; 6 | import CollectionRenameForm from './CollectionRenameForm'; 7 | import CollectionExportForm from './CollectionExportForm'; 8 | import { 9 | toggleIsEditingName, 10 | removeAllTweetsFromCollection 11 | } from '../actions'; 12 | 13 | class CollectionControls extends Component { 14 | getHeaderText = () => { 15 | const { numberOfTweetsInCollection } = this.props; 16 | const { collectionName } = this.props; 17 | let text = numberOfTweetsInCollection; 18 | 19 | if (numberOfTweetsInCollection === 1) { 20 | text = `${text} tweet in your`; 21 | } else { 22 | text = `${text} tweets in your`; 23 | } 24 | 25 | return ( 26 | 27 | {text} {collectionName} collection 28 | 29 | ); 30 | } 31 | 32 | render() { 33 | const { 34 | collectionName, 35 | isEditingName, 36 | htmlMarkup, 37 | onRenameCollection, 38 | onEmptyCollection 39 | } = this.props; 40 | 41 | if (isEditingName) { 42 | return ( 43 | 44 | ); 45 | } 46 | 47 | return ( 48 |
49 |
50 | 51 |
66 | ); 67 | } 68 | } 69 | 70 | const mapStateToProps = state => state.collection; 71 | 72 | const mapDispatchToProps = dispatch => ({ 73 | onRenameCollection: () => { 74 | dispatch(toggleIsEditingName()); 75 | }, 76 | onEmptyCollection: () => { 77 | dispatch(removeAllTweetsFromCollection()); 78 | } 79 | }); 80 | 81 | export default connect( 82 | mapStateToProps, 83 | mapDispatchToProps 84 | )(CollectionControls); 85 | -------------------------------------------------------------------------------- /Chapter12/snapterest/source/components/CollectionExportForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const formStyle = { 4 | display: 'inline-block' 5 | }; 6 | 7 | const CollectionExportForm = (props) => ( 8 |
12 | 13 | 14 | 17 |
18 | ); 19 | 20 | export default CollectionExportForm; 21 | -------------------------------------------------------------------------------- /Chapter12/snapterest/source/components/CollectionRenameForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Header from './Header'; 5 | import Button from './Button'; 6 | import { 7 | setCollectionName, 8 | setEditingName, 9 | toggleIsEditingName 10 | } from '../actions'; 11 | 12 | const inputStyle = { 13 | marginRight: '5px' 14 | }; 15 | 16 | class CollectionRenameForm extends Component { 17 | componentDidMount() { 18 | this.refs.collectionName.focus(); 19 | } 20 | 21 | render() { 22 | const { 23 | editingName, 24 | onNameChange, 25 | onSubmit, 26 | onCancel 27 | } = this.props; 28 | 29 | return ( 30 |
31 |
32 | 33 |
34 | 41 |
42 | 43 |