├── .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 |
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 |
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 |
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) => {v}
);
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 |
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 |
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 |
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 |
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 |