├── .gitignore
├── 01-hello-world
├── hello-world-1.html
└── hello-world-2-with-DOM-attribute.html
├── 02-first-component-es5
└── first-component-es5.html
├── 03-first-component-es6-jsx
├── .gitignore
├── README.md
├── package.json
├── public
│ └── index.html
└── src
│ └── index.js
├── 04-es6-examples
├── arrow-functions-expression-bodies.js
├── arrow-functions-lexical-this.js
├── classes.js
├── const.js
├── default-parameter.js
├── destructuring-array-matching.js
├── destructuring-parameter-context-matching.js
├── let.js
├── module-import-export.js
├── spread-operator.js
└── template-strings.js
├── 05-jsx-examples
└── comparison-jsx-js.js
├── 06-component-composition
├── .gitignore
├── README.md
├── package.json
├── public
│ └── index.html
└── src
│ └── index.js
├── 07-component-props
├── .gitignore
├── README.md
├── package.json
├── proptype_validators.js
├── public
│ └── index.html
└── src
│ └── index.js
├── 08-component-state
├── .gitignore
├── README.md
├── package.json
├── public
│ └── index.html
└── src
│ └── index.js
├── 09-component-lifecycle
├── .gitignore
├── README.md
├── package.json
├── public
│ └── index.html
└── src
│ ├── App.js
│ ├── Counter.js
│ └── index.js
├── 10-events
├── .gitignore
├── README.md
├── difference-html-react.js
├── package.json
├── public
│ └── index.html
└── src
│ └── index.js
├── 11-keys
├── .gitignore
├── README.md
├── package.json
├── public
│ └── index.html
└── src
│ └── index.js
├── 12-redux
├── .gitignore
├── README.md
├── package.json
├── public
│ └── index.html
└── src
│ ├── actions
│ ├── authorActions.js
│ └── recipeActions.js
│ ├── components
│ ├── AddRecipe.js
│ ├── App.js
│ ├── ChangeAuthor.js
│ ├── RecipeList.js
│ └── RecipeListItem.js
│ ├── index.js
│ └── reducers
│ ├── author.js
│ ├── index.js
│ └── recipes.js
├── 12b-data-fetching-redux
├── .gitignore
├── README.md
├── package.json
├── public
│ └── index.html
└── src
│ ├── actions
│ ├── authorActions.js
│ └── recipeActions.js
│ ├── api
│ ├── config.js
│ └── db.json
│ ├── components
│ ├── AddRecipe.js
│ ├── App.js
│ ├── ChangeAuthor.js
│ ├── RecipeList.js
│ └── RecipeListItem.js
│ ├── index.js
│ └── reducers
│ ├── author.js
│ ├── index.js
│ └── recipes.js
├── 13-react-router
├── .gitignore
├── README.md
├── package.json
├── public
│ └── index.html
└── src
│ ├── components
│ ├── App.js
│ ├── Header.js
│ ├── Home.js
│ ├── RecipeDetail.js
│ ├── RecipeOverview.js
│ └── Recipes.js
│ └── index.js
├── 14-react-build-process
├── .gitignore
├── README.md
├── config
│ ├── env.js
│ ├── jest
│ │ ├── cssTransform.js
│ │ └── fileTransform.js
│ ├── paths.js
│ ├── polyfills.js
│ ├── webpack.config.dev.js
│ ├── webpack.config.prod.js
│ └── webpackDevServer.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── scripts
│ ├── build.js
│ ├── start.js
│ └── test.js
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ └── registerServiceWorker.js
├── 15-conference-application
├── index.html
├── step0-initial
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── components
│ │ └── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── img
│ │ └── logo.svg
│ │ ├── index.css
│ │ └── index.js
├── step1-props
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ └── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── index.css
│ │ └── index.js
├── step2-state
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ ├── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── TalkItem
│ │ │ ├── TalkItem.css
│ │ │ └── TalkItem.js
│ │ └── TalkOverview
│ │ │ ├── TalkOverview.css
│ │ │ └── TalkOverview.js
│ │ ├── index.css
│ │ └── index.js
├── step3-router
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ ├── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── NotFound
│ │ │ └── NotFound.js
│ │ ├── TalkItem
│ │ │ ├── TalkItem.css
│ │ │ └── TalkItem.js
│ │ └── TalkOverview
│ │ │ ├── TalkOverview.css
│ │ │ └── TalkOverview.js
│ │ ├── index.css
│ │ └── index.js
├── step4-more-state-optional
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Favourites
│ │ │ ├── Favourites.css
│ │ │ └── Favourites.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ ├── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── NotFound
│ │ │ └── NotFound.js
│ │ ├── TalkItem
│ │ │ ├── TalkItem.css
│ │ │ └── TalkItem.js
│ │ └── TalkOverview
│ │ │ ├── TalkOverview.css
│ │ │ └── TalkOverview.js
│ │ ├── img
│ │ └── star.png
│ │ ├── index.css
│ │ └── index.js
├── step5a-data-fetching-external-API
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ ├── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── NotFound
│ │ │ └── NotFound.js
│ │ ├── TalkItem
│ │ │ ├── TalkItem.css
│ │ │ └── TalkItem.js
│ │ └── TalkOverview
│ │ │ ├── TalkOverview.css
│ │ │ └── TalkOverview.js
│ │ ├── index.css
│ │ └── index.js
├── step5b-data-fetching-external-API-with-favourites
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Favourites
│ │ │ ├── Favourites.css
│ │ │ └── Favourites.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ ├── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── NotFound
│ │ │ └── NotFound.js
│ │ ├── TalkItem
│ │ │ ├── TalkItem.css
│ │ │ └── TalkItem.js
│ │ └── TalkOverview
│ │ │ ├── TalkOverview.css
│ │ │ └── TalkOverview.js
│ │ ├── img
│ │ └── star.png
│ │ ├── index.css
│ │ └── index.js
├── step5c-data-fetching-local-API
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── api
│ │ ├── config.js
│ │ └── db.json
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ ├── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── NotFound
│ │ │ └── NotFound.js
│ │ ├── TalkItem
│ │ │ ├── TalkItem.css
│ │ │ └── TalkItem.js
│ │ └── TalkOverview
│ │ │ ├── TalkOverview.css
│ │ │ └── TalkOverview.js
│ │ ├── index.css
│ │ └── index.js
├── step6a-redux-external-API
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── actions
│ │ ├── daysActions.js
│ │ └── talksActions.js
│ │ ├── api
│ │ └── config.js
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ ├── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── NotFound
│ │ │ └── NotFound.js
│ │ ├── TalkItem
│ │ │ ├── TalkItem.css
│ │ │ └── TalkItem.js
│ │ └── TalkOverview
│ │ │ ├── TalkOverview.css
│ │ │ └── TalkOverview.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── reducers
│ │ ├── days.js
│ │ ├── index.js
│ │ └── talks.js
├── step6b-redux-external-API-with-favourites
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── actions
│ │ ├── daysActions.js
│ │ ├── favouritesActions.js
│ │ └── talksActions.js
│ │ ├── api
│ │ └── config.js
│ │ ├── components
│ │ ├── App
│ │ │ ├── App.css
│ │ │ └── App.js
│ │ ├── Favourites
│ │ │ ├── Favourites.css
│ │ │ └── Favourites.js
│ │ ├── Header
│ │ │ ├── Header.css
│ │ │ └── Header.js
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── Home.js
│ │ ├── Navigation
│ │ │ ├── Navigation.css
│ │ │ └── Navigation.js
│ │ ├── NavigationButton
│ │ │ ├── NavigationButton.css
│ │ │ └── NavigationButton.js
│ │ ├── NotFound
│ │ │ └── NotFound.js
│ │ ├── TalkItem
│ │ │ ├── TalkItem.css
│ │ │ └── TalkItem.js
│ │ └── TalkOverview
│ │ │ ├── TalkOverview.css
│ │ │ └── TalkOverview.js
│ │ ├── img
│ │ └── star.png
│ │ ├── index.css
│ │ ├── index.js
│ │ └── reducers
│ │ ├── days.js
│ │ ├── favourites.js
│ │ ├── index.js
│ │ └── talks.js
└── step6c-redux-local-API
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ └── index.html
│ └── src
│ ├── actions
│ ├── daysActions.js
│ └── talksActions.js
│ ├── api
│ ├── config.js
│ └── db.json
│ ├── components
│ ├── App
│ │ ├── App.css
│ │ └── App.js
│ ├── Header
│ │ ├── Header.css
│ │ └── Header.js
│ ├── Home
│ │ ├── Home.css
│ │ └── Home.js
│ ├── Navigation
│ │ ├── Navigation.css
│ │ └── Navigation.js
│ ├── NavigationButton
│ │ ├── NavigationButton.css
│ │ └── NavigationButton.js
│ ├── NotFound
│ │ └── NotFound.js
│ ├── TalkItem
│ │ ├── TalkItem.css
│ │ └── TalkItem.js
│ └── TalkOverview
│ │ ├── TalkOverview.css
│ │ └── TalkOverview.js
│ ├── index.css
│ ├── index.js
│ └── reducers
│ ├── days.js
│ ├── index.js
│ └── talks.js
├── ConferenceApp_screenshot.png
└── Readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode
--------------------------------------------------------------------------------
/01-hello-world/hello-world-1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1 - Hello World
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/01-hello-world/hello-world-2-with-DOM-attribute.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1 - Hello World
5 |
6 |
7 |
8 |
9 |
10 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/02-first-component-es5/first-component-es5.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 2 - First Component - ES5
5 |
6 |
7 |
8 |
9 |
10 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/03-first-component-es6-jsx/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/03-first-component-es6-jsx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "react": "^15.5.4",
10 | "react-dom": "^15.5.4"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/03-first-component-es6-jsx/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 3 - First Component - ES6 - JSX
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/03-first-component-es6-jsx/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | class MyComponent extends React.Component {
5 | render() {
6 | return (
7 | Hello First Component with ES6 & JSX!
8 | );
9 | }
10 | }
11 |
12 | ReactDOM.render(
13 | ,
14 | document.getElementById('mount')
15 | );
16 |
17 |
18 |
--------------------------------------------------------------------------------
/04-es6-examples/arrow-functions-expression-bodies.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#ExpressionBodies
2 | // in ES6
3 | odds = evens.map(v => v + 1);
4 |
5 | // in ES5
6 | odds = evens.map(function (v) { return v + 1; });
7 |
--------------------------------------------------------------------------------
/04-es6-examples/arrow-functions-lexical-this.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#Lexicalthis
2 | // in ES6
3 | this.nums.forEach((v) => {
4 | if (v % 5 === 0)
5 | this.fives.push(v);
6 | })
7 |
8 | // in ES5
9 | // variant 1
10 | var self = this;
11 | this.nums.forEach(function (v) {
12 | if (v % 5 === 0)
13 | self.fives.push(v);
14 | });
15 |
16 | // variant 2
17 | this.nums.forEach(function (v) {
18 | if (v % 5 === 0)
19 | this.fives.push(v);
20 | }, this);
21 |
22 | // variant 3 (since ECMAScript 5.1 only)
23 | this.nums.forEach(function (v) {
24 | if (v % 5 === 0)
25 | this.fives.push(v);
26 | }.bind(this));
27 |
28 |
--------------------------------------------------------------------------------
/04-es6-examples/classes.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#ClassDefinition
2 | // in ES6
3 | class Shape {
4 | constructor (id, x, y) {
5 | this.id = id;
6 | this.move(x, y);
7 | }
8 | move (x, y) {
9 | this.x = x;
10 | this.y = y;
11 | }
12 | }
13 |
14 | // in ES5
15 | var Shape = function (id, x, y) {
16 | this.id = id;
17 | this.move(x, y);
18 | };
19 | Shape.prototype.move = function (x, y) {
20 | this.x = x;
21 | this.y = y;
22 | };
23 |
--------------------------------------------------------------------------------
/04-es6-examples/const.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#Constants
2 | // in ES6
3 | const PI = 3.141593;
4 |
5 | // in ES5
6 | // only through the help of object properties
7 | // and only in global context and not in a block scope
8 | Object.defineProperty(typeof global === "object" ? global : window, "PI", {
9 | value: 3.141593,
10 | enumerable: true,
11 | writable: false,
12 | configurable: false
13 | })
14 |
15 |
--------------------------------------------------------------------------------
/04-es6-examples/default-parameter.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#DefaultParameterValues
2 | // in ES6
3 | function f (x, y = 7, z = 42) {
4 | return x + y + z;
5 | }
6 | f(1) === 50;
7 |
8 | // in ES5
9 | function f (x, y, z) {
10 | if (y === undefined)
11 | y = 7;
12 | if (z === undefined)
13 | z = 42;
14 | return x + y + z;
15 | };
16 | f(1) === 50;
--------------------------------------------------------------------------------
/04-es6-examples/destructuring-array-matching.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#ArrayMatching
2 | // in ES6
3 | var list = [ 1, 2, 3 ];
4 | var [ a, , b ] = list;
5 |
6 | // in ES5
7 | var list = [ 1, 2, 3 ];
8 | var a = list[0];
9 | var b = list[2];
10 |
--------------------------------------------------------------------------------
/04-es6-examples/destructuring-parameter-context-matching.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#ParameterContextMatching
2 | // in ES6
3 | function test ({ name, val }) {
4 | console.log(name, val);
5 | }
6 | test({ name: "bar", val: 42 });
7 |
8 | // in ES5
9 | function test (arg) {
10 | var name = arg.name;
11 | var val = arg.val;
12 | console.log(name, val);
13 | };
14 | test({ name: "bar", val: 42 });
15 |
16 |
--------------------------------------------------------------------------------
/04-es6-examples/let.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#BlockScopedVariables
2 | // in ES6
3 | function letTest() {
4 | let x = 31;
5 | if (true) {
6 | let x = 71; // other variable
7 | console.log(x); // 71
8 | }
9 | console.log(x); // 31
10 | }
11 |
12 | // in ES5
13 | function varTest() {
14 | var x = 31;
15 | if (true) {
16 | var x = 71; // same variable!
17 | console.log(x); // 71
18 | }
19 | console.log(x); // 71
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/04-es6-examples/module-import-export.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#ValueExportImport
2 | // in ES6
3 | // lib/math.js
4 | export function sum (x, y) { return x + y };
5 | export var pi = 3.141593;
6 |
7 | // someApp.js
8 | import * as math from "lib/math";
9 | console.log("2π = " + math.sum(math.pi, math.pi));
10 |
11 | // otherApp.js
12 | import { sum, pi } from "lib/math";
13 | console.log("2π = " + sum(pi, pi));
14 |
15 | // in ES5
16 | // lib/math.js
17 | LibMath = {};
18 | LibMath.sum = function (x, y) { return x + y };
19 | LibMath.pi = 3.141593;
20 |
21 | // someApp.js
22 | var math = LibMath;
23 | console.log("2π = " + math.sum(math.pi, math.pi));
24 |
25 | // otherApp.js
26 | var sum = LibMath.sum, pi = LibMath.pi;
27 | console.log("2π = " + sum(pi, pi));
28 |
29 |
--------------------------------------------------------------------------------
/04-es6-examples/spread-operator.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#SpreadOperator
2 | // in ES6
3 | var params = [ "hello", true, 7 ];
4 | var other = [ 1, 2, ...params ]; // [ 1, 2, "hello", true, 7 ]
5 |
6 | // in ES5
7 | var params = [ "hello", true, 7 ];
8 | var other = [ 1, 2 ].concat(params); // [ 1, 2, "hello", true, 7 ]
9 |
--------------------------------------------------------------------------------
/04-es6-examples/template-strings.js:
--------------------------------------------------------------------------------
1 | // Full Reference: http://es6-features.org/#StringInterpolation
2 | // in ES6
3 | var user = { name: 'Tom' };
4 | var myString = `Hello ${user.name}!`;
5 |
6 | // in ES5
7 | var user = { name: `Tom` };
8 | var myString = "Hello " + user.name + "!";
9 |
10 |
--------------------------------------------------------------------------------
/05-jsx-examples/comparison-jsx-js.js:
--------------------------------------------------------------------------------
1 | // Built-in component
2 | // JSX code
3 | Hello World!
4 |
5 | // compiled JS code
6 | React.createElement(h1, {id: 'myTitle'}, 'Hello World!');
7 |
8 |
9 |
10 | // User-Defined component
11 | // JSX code
12 | Click Me
13 |
14 | // compiled JS code
15 | React.createElement(MyButton, {size: 'big'}, 'Click Me');
16 |
17 |
--------------------------------------------------------------------------------
/06-component-composition/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/06-component-composition/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "react": "^15.5.4",
10 | "react-dom": "^15.5.4"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/06-component-composition/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 6 - Component Composition
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/06-component-composition/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | class UserDetails extends React.Component {
5 | render(){
6 | return (
7 |
8 |
User Details
9 |
Name: Tim
10 |
11 | );
12 | }
13 | }
14 |
15 | class UserList extends React.Component {
16 | render() {
17 | return (
18 |
19 |
20 |
21 |
22 | );
23 | }
24 | }
25 |
26 | ReactDOM.render(, document.getElementById('mount'));
27 |
28 |
29 |
--------------------------------------------------------------------------------
/07-component-props/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/07-component-props/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "prop-types": "^15.5.8",
10 | "react": "^15.5.4",
11 | "react-dom": "^15.5.4"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/07-component-props/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 7 - Component Props
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/07-component-props/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PropTypes from 'prop-types';
4 |
5 | class UserDetails extends React.Component {
6 | render(){
7 | return (
8 |
9 |
User Details
10 |
Name: {this.props.name}
11 |
12 | );
13 | }
14 | }
15 | UserDetails.defaultProps = {
16 | name: "Mike"
17 | };
18 | UserDetails.propTypes = {
19 | name: PropTypes.string
20 | };
21 |
22 | class UserList extends React.Component {
23 | render() {
24 | return (
25 |
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 |
34 | ReactDOM.render(, document.getElementById('mount'));
35 |
36 |
37 |
--------------------------------------------------------------------------------
/08-component-state/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/08-component-state/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "prop-types": "^15.5.8",
10 | "react": "^15.5.4",
11 | "react-dom": "^15.5.4"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/08-component-state/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 8 - Component State
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/08-component-state/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | // stateless component
5 | const CounterText = ({ count }) => {
6 | return (Counter: {count}
)
7 | }
8 |
9 | // stateful component
10 | // class CounterText extends React.Component {
11 | // render(){
12 | // return (Counter: {this.props.count}
);
13 | // }
14 | // }
15 |
16 | class Counter extends React.Component {
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | counter: 0
21 | };
22 | }
23 | updateCounter() {
24 | this.setState((prevState, props) => ({
25 | counter: prevState.counter + 1
26 | }));
27 | }
28 | render() {
29 | return (
30 |
31 |
32 |
35 |
36 | );
37 | }
38 | }
39 |
40 | ReactDOM.render(, document.getElementById('mount'));
41 |
42 |
43 |
--------------------------------------------------------------------------------
/09-component-lifecycle/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/09-component-lifecycle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "prop-types": "^15.5.8",
10 | "react": "^15.5.4",
11 | "react-dom": "^15.5.4"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/09-component-lifecycle/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 9 - Component Lifecycle
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/09-component-lifecycle/src/Counter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const color = 'color: green';
4 |
5 | class Counter extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | console.log('%cCounter - constructor()', color);
9 | this.state = {
10 | counter: 0
11 | };
12 | }
13 | componentWillMount(){
14 | console.log('%cCounter - componentWillMount()', color);
15 | }
16 | componentDidMount(){
17 | console.log('%cCounter - componentDidMount()', color);
18 | }
19 | componentWillReceiveProps(nextProps){
20 | console.log('%cCounter - componentWillReceiveProps()', color);
21 | }
22 | shouldComponentUpdate(nextProps, nextState){
23 | console.log('%cCounter - shouldComponentUpdate()', color);
24 | return true;
25 | }
26 | componentWillUpdate(){
27 | console.log('%cCounter - componentWillUpdate()', color);
28 | }
29 | componentDidUpdate(){
30 | console.log('%cCounter - componentDidUpdate()', color);
31 | }
32 | componentWillUnmount(){
33 | console.log('%cCounter - componentWillUnmount()', color);
34 | }
35 | updateCounter() {
36 | console.log('%c--- Set state within Counter Component ---', color);
37 | this.setState((prevState, props) => ({
38 | counter: prevState.counter + this.props.counterOperation
39 | }));
40 | }
41 | render() {
42 | console.log('%cCounter - render()', color);
43 | return (
44 |
45 |
Counter: {this.state.counter}
46 |
Operation: { this.props.counterOperation === -1 ? 'Decrement' : 'Increment' }
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | export default Counter;
--------------------------------------------------------------------------------
/09-component-lifecycle/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('mount'));
6 |
7 |
8 |
--------------------------------------------------------------------------------
/10-events/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/10-events/difference-html-react.js:
--------------------------------------------------------------------------------
1 | // Reference: https://facebook.github.io/react/docs/handling-events.html
2 | // HTML
3 |
6 |
7 |
8 | // React
9 |
12 |
--------------------------------------------------------------------------------
/10-events/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "prop-types": "^15.5.8",
10 | "react": "^15.5.4",
11 | "react-dom": "^15.5.4"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/10-events/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 10 - Events
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/10-events/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | class App extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | counter: 0,
9 | name: 'Tim'
10 | };
11 | this.updateCounter = this.updateCounter.bind(this);
12 | }
13 | // this binding in constructor
14 | updateCounter(e) {
15 | let factor = 1;
16 | if (e.shiftKey) {
17 | factor = 10;
18 | }
19 | this.setState((prevState, props) => ({
20 | counter: prevState.counter + factor
21 | }));
22 | }
23 | // this binding with arrow function
24 | updateInput(e){
25 | this.setState({ name: e.target.value });
26 | }
27 | render() {
28 | return (
29 |
30 |
31 |
Counter: {this.state.counter}
32 |
35 |
36 |
37 |
38 |
Name: {this.state.name}
39 |
this.updateInput(e)} />
42 |
43 |
44 | );
45 | }
46 | }
47 |
48 | ReactDOM.render(, document.getElementById('mount'));
49 |
50 |
51 |
--------------------------------------------------------------------------------
/11-keys/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/11-keys/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "prop-types": "^15.5.8",
10 | "react": "^15.5.4",
11 | "react-dom": "^15.5.4"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/11-keys/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 11 - Keys
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/11-keys/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | class App extends React.Component {
5 | render() {
6 | const data = [
7 | { id: 'A1', text: 'My awesome item'},
8 | { id: 'B7', text: 'My premium item'},
9 | { id: 'C9', text: 'My wonderful item'}
10 | ];
11 | const listItems = data.map((item) =>
12 |
13 | {item.text}
14 |
15 | );
16 | return (
17 |
20 | );
21 | }
22 | }
23 |
24 | ReactDOM.render(, document.getElementById('mount'));
25 |
26 |
27 |
--------------------------------------------------------------------------------
/12-redux/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/12-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "prop-types": "^15.5.8",
10 | "react": "^15.5.4",
11 | "react-dom": "^15.5.4",
12 | "react-redux": "^5.0.5",
13 | "redux": "^3.6.0"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test --env=jsdom",
19 | "eject": "react-scripts eject"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/12-redux/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 12a - Redux Data Fetching
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/12-redux/src/actions/authorActions.js:
--------------------------------------------------------------------------------
1 | export const updateAuthor = (name) => {
2 | return {
3 | type: 'UPDATE_AUTHOR',
4 | payload: name
5 | }
6 | }
--------------------------------------------------------------------------------
/12-redux/src/actions/recipeActions.js:
--------------------------------------------------------------------------------
1 | export const addRecipe = (recipe) => {
2 | return {
3 | type: 'ADD_RECIPE',
4 | payload: recipe
5 | }
6 | }
--------------------------------------------------------------------------------
/12-redux/src/components/AddRecipe.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { addRecipe } from '../actions/recipeActions';
4 |
5 | class AddRecipe extends React.Component{
6 | constructor(props){
7 | super(props);
8 | this.state = {
9 | name: '',
10 | time: ''
11 | }
12 | this.saveRecipe = this.saveRecipe.bind(this);
13 | }
14 | saveRecipe(){
15 | this.props.saveRecipeToStore(this.state);
16 | this.setState({ name: '', time: '' });
17 | }
18 | render(){
19 | return (
20 |
21 |
Add a new Recipe
22 |
23 |
24 | this.setState({ name: e.target.value })} />
25 |
26 |
27 |
28 | this.setState({ time: e.target.value })} />
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | const mapDispatchToProps = (dispatch) => {
37 | return {
38 | saveRecipeToStore: (recipe) => dispatch(addRecipe(recipe))
39 | }
40 | }
41 |
42 | export default connect(null, mapDispatchToProps)(AddRecipe)
--------------------------------------------------------------------------------
/12-redux/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RecipeList from './RecipeList';
3 | import AddRecipe from './AddRecipe';
4 | import ChangeAuthor from './ChangeAuthor';
5 |
6 | const App = () => {
7 | return (
8 |
9 |
Recipe Book
10 |
11 |
12 |
13 |
14 | );
15 | }
16 |
17 | export default App;
--------------------------------------------------------------------------------
/12-redux/src/components/ChangeAuthor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { updateAuthor } from '../actions/authorActions';
4 |
5 | class AddRecipe extends React.Component{
6 | constructor(props){
7 | super(props);
8 | this.state = {
9 | name: this.props.authorName,
10 | }
11 | this.changeAuthor = this.changeAuthor.bind(this);
12 | }
13 | componentWillReceiveProps(newProps) {
14 | if(this.props.authorName !== newProps.authorName) {
15 | this.setState({ name: newProps.authorName });
16 | }
17 | }
18 | changeAuthor(){
19 | this.props.saveAuthorToStore(this.state.name);
20 | this.setState({ name: '' });
21 | }
22 | render(){
23 | return (
24 |
25 |
Change Author
26 |
27 |
28 | this.setState({ name: e.target.value })} />
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 | const mapStateToProps = (state) => {
36 | return {
37 | authorName: state.author.name
38 | }
39 | }
40 |
41 | const mapDispatchToProps = (dispatch) => {
42 | return {
43 | saveAuthorToStore: (name) => dispatch(updateAuthor(name))
44 | }
45 | }
46 |
47 | export default connect(mapStateToProps, mapDispatchToProps)(AddRecipe)
--------------------------------------------------------------------------------
/12-redux/src/components/RecipeList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RecipeListItem from './RecipeListItem';
3 | import { connect } from 'react-redux';
4 |
5 | class RecipeList extends React.Component{
6 | render(){
7 | const list = this.props.recipeList.map((recipe, index) => {
8 | return (
9 |
10 |
11 |
12 | );
13 | });
14 | return (
15 |
16 |
By { this.props.authorName }
17 |
18 |
19 | );
20 | }
21 | }
22 |
23 | const mapStateToProps = (state, ownProps) => {
24 | return {
25 | recipeList: state.recipes.list,
26 | authorName: state.author.name
27 | }
28 | }
29 |
30 | export default connect(mapStateToProps, null)(RecipeList)
--------------------------------------------------------------------------------
/12-redux/src/components/RecipeListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const RecipeListItem = ({ name, time}) => {
4 | return ({name} ({time})
);
5 | }
6 |
7 | export default RecipeListItem;
--------------------------------------------------------------------------------
/12-redux/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './components/App';
4 |
5 | import { Provider } from 'react-redux'
6 | import { createStore, compose } from 'redux'
7 | import reducers from './reducers';
8 |
9 |
10 | // create store and use redux dev tool middleware as browser extension
11 | const store = createStore(
12 | reducers,
13 | compose(window.devToolsExtension ? window.devToolsExtension() : f => f)
14 | )
15 | // or create store without dev tools
16 | // let store = createStore(reducers);
17 |
18 | ReactDOM.render(
19 |
20 |
21 | ,
22 | document.getElementById('mount')
23 | );
24 |
25 |
26 |
--------------------------------------------------------------------------------
/12-redux/src/reducers/author.js:
--------------------------------------------------------------------------------
1 | // author reducer
2 |
3 | const initialState = {
4 | name: 'Kristin'
5 | }
6 |
7 | export default (state = initialState, action) => {
8 | switch (action.type) {
9 | case 'UPDATE_AUTHOR': {
10 | return { ...state, name: action.payload };
11 | }
12 | default:
13 | return state;
14 | }
15 | };
--------------------------------------------------------------------------------
/12-redux/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import recipes from './recipes';
3 | import author from './author';
4 |
5 | const reducers = combineReducers({
6 | recipes,
7 | author
8 | })
9 |
10 | export default reducers;
--------------------------------------------------------------------------------
/12-redux/src/reducers/recipes.js:
--------------------------------------------------------------------------------
1 | // recipe reducer
2 |
3 | const initialState = {
4 | list: [
5 | {
6 | name: 'Hackbraten',
7 | time: '1 Stunde',
8 | }
9 | ]
10 | }
11 |
12 | export default (state = initialState, action) => {
13 | switch (action.type) {
14 | case 'ADD_RECIPE':
15 | return { ...state, list: [ ...state.list, action.payload] };
16 | default:
17 | return state;
18 | }
19 | };
--------------------------------------------------------------------------------
/12b-data-fetching-redux/.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
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/12b-data-fetching-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3-first-component-es6-jsx",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.9.5"
7 | },
8 | "dependencies": {
9 | "json-server": "^0.10.1",
10 | "prop-types": "^15.5.8",
11 | "react": "^15.5.4",
12 | "react-dom": "^15.5.4",
13 | "react-redux": "^5.0.5",
14 | "redux": "^3.6.0",
15 | "redux-thunk": "^2.2.0"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test --env=jsdom",
21 | "eject": "react-scripts eject",
22 | "api": "json-server --watch src/api/db.json --port 3004"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/12b-data-fetching-redux/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 12 - Redux
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/actions/authorActions.js:
--------------------------------------------------------------------------------
1 | import { config } from '../api/config';
2 |
3 | export const writeAuthor = (name) => {
4 | return {
5 | type: 'UPDATE_AUTHOR',
6 | payload: name
7 | }
8 | }
9 |
10 | export const saveAuthor = (data) => {
11 | return dispatch => {
12 | return fetch(`${config.API_URL}/author`, {
13 | method: 'PUT',
14 | headers: {
15 | 'Accept': 'application/json',
16 | 'Content-Type': 'application/json'
17 | },
18 | body: JSON.stringify({
19 | "name": data,
20 | })
21 | })
22 | .then(response => response.json())
23 | .then(json => dispatch(writeAuthor(data)))
24 | }
25 | }
26 |
27 | export const requestAuthor = () => {
28 | return {
29 | type: 'REQUEST_AUTHOR',
30 | payload: {
31 | loading: true
32 | }
33 | }
34 | }
35 |
36 | export const receiveAuthor = (data) => {
37 | return {
38 | type: 'RECEIVE_AUTHOR',
39 | payload: {
40 | data: data.name,
41 | loading: false
42 | }
43 | }
44 | }
45 |
46 | export const fetchAuthor = () => {
47 | return dispatch => {
48 | // set loading state
49 | dispatch(requestAuthor());
50 | // fetch data from API
51 | return fetch(`${config.API_URL}/author`)
52 | .then(response => response.json())
53 | .then(json => dispatch(receiveAuthor(json)))
54 | .catch((err) => console.error(err))
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/actions/recipeActions.js:
--------------------------------------------------------------------------------
1 | import { config } from '../api/config';
2 |
3 | export const addRecipe = (recipe) => {
4 | return {
5 | type: 'ADD_RECIPE',
6 | payload: recipe
7 | }
8 | }
9 |
10 | export const saveRecipe = (newRecipe) => {
11 | return (dispatch, getState) => {
12 | return fetch(`${config.API_URL}/recipes`, {
13 | method: 'PUT',
14 | headers: {
15 | 'Accept': 'application/json',
16 | 'Content-Type': 'application/json'
17 | },
18 | body: JSON.stringify({
19 | "list": [...getState().recipes.list, newRecipe],
20 | })
21 | })
22 | .then(response => response.json())
23 | .then(json => dispatch(addRecipe(newRecipe)))
24 | }
25 | }
26 |
27 | export const requestRecipes = () => {
28 | return {
29 | type: 'REQUEST_RECIPES',
30 | payload: {
31 | loading: true
32 | }
33 | }
34 | }
35 |
36 | export const receiveRecipes = (data) => {
37 | return {
38 | type: 'RECEIVE_RECIPES',
39 | payload: {
40 | data: data.list,
41 | loading: false
42 | }
43 | }
44 | }
45 |
46 | export const fetchRecipes = () => {
47 | return dispatch => {
48 | // set loading state
49 | dispatch(requestRecipes());
50 | // fetch data from API
51 | return fetch(`${config.API_URL}/recipes`)
52 | .then(response => response.json())
53 | .then(json => dispatch(receiveRecipes(json)))
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/api/config.js:
--------------------------------------------------------------------------------
1 | export const config = {
2 | API_URL: 'http://localhost:3004',
3 | }
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/api/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "recipes": {
3 | "list": [
4 | {
5 | "name": "Hack",
6 | "time": "1 Stunde"
7 | },
8 | {
9 | "name": "Nudelsalat",
10 | "time": "20 Minuten"
11 | }
12 | ]
13 | },
14 | "author": {
15 | "name": "Kristin"
16 | }
17 | }
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/components/AddRecipe.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { saveRecipe } from '../actions/recipeActions';
4 |
5 | class AddRecipe extends React.Component{
6 | constructor(props){
7 | super(props);
8 | this.state = {
9 | name: '',
10 | time: ''
11 | }
12 | this.saveRecipe = this.saveRecipe.bind(this);
13 | }
14 | saveRecipe(){
15 | this.props.saveRecipe(this.state);
16 | this.setState({ name: '', time: '' });
17 | }
18 | render(){
19 | return (
20 |
21 |
Add a new Recipe
22 |
23 |
24 | this.setState({ name: e.target.value })} />
25 |
26 |
27 |
28 | this.setState({ time: e.target.value })} />
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | const mapDispatchToProps = (dispatch) => {
37 | return {
38 | saveRecipe: (newRecipe) => dispatch(saveRecipe(newRecipe))
39 | }
40 | }
41 |
42 | export default connect(null, mapDispatchToProps)(AddRecipe)
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RecipeList from './RecipeList';
3 | import AddRecipe from './AddRecipe';
4 | import ChangeAuthor from './ChangeAuthor';
5 |
6 | const App = () => {
7 | return (
8 |
9 |
Recipe Book
10 |
11 |
12 |
13 |
14 | );
15 | }
16 |
17 | export default App;
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/components/ChangeAuthor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { saveAuthor } from '../actions/authorActions';
4 |
5 | class AddRecipe extends React.Component{
6 | constructor(props){
7 | super(props);
8 | this.state = {
9 | name: this.props.authorName,
10 | }
11 | this.changeAuthor = this.changeAuthor.bind(this);
12 | }
13 | componentWillReceiveProps(newProps) {
14 | if(this.props.authorName !== newProps.authorName) {
15 | this.setState({ name: newProps.authorName });
16 | }
17 | }
18 | changeAuthor(){
19 | this.props.saveAuthor(this.state.name);
20 | this.setState({ name: '' });
21 | }
22 | render(){
23 | return (
24 |
25 |
Change Author
26 |
27 |
28 | this.setState({ name: e.target.value })} />
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 | const mapStateToProps = (state) => {
36 | return {
37 | authorName: state.author.name
38 | }
39 | }
40 |
41 | const mapDispatchToProps = (dispatch) => {
42 | return {
43 | saveAuthor: (data) => dispatch(saveAuthor(data)),
44 | }
45 | }
46 |
47 | export default connect(mapStateToProps, mapDispatchToProps)(AddRecipe)
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/components/RecipeList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RecipeListItem from './RecipeListItem';
3 | import { connect } from 'react-redux';
4 | import { fetchRecipes } from '../actions/recipeActions';
5 | import { fetchAuthor } from '../actions/authorActions';
6 |
7 |
8 | class RecipeList extends React.Component{
9 | componentDidMount(){
10 | this.props.fetchAllRecipes();
11 | this.props.fetchAuthor();
12 | }
13 |
14 | renderRecipes(){
15 | const { recipeLoading, recipeList } = this.props;
16 | if(recipeLoading){
17 | return (Loading recipes...
)
18 | }
19 | if(!recipeList || recipeList.length === 0){
20 | return (No recipes loaded.
)
21 | }
22 | const list = recipeList.map((recipe, index) => {
23 | return (
24 |
25 |
26 |
27 | );
28 | });
29 | return ();
30 | }
31 |
32 | renderAuthor(){
33 | const { authorName } = this.props;
34 | if(!authorName) {
35 | return (Loading author...
)
36 | }
37 | return (By { authorName }
);
38 | }
39 |
40 | render(){
41 | return (
42 |
43 | { this.renderAuthor() }
44 | { this.renderRecipes() }
45 |
46 | );
47 | }
48 | }
49 |
50 | const mapStateToProps = (state, ownProps) => {
51 | return {
52 | recipeList: state.recipes.list,
53 | recipeLoading: state.recipes.loading,
54 | authorName: state.author.name
55 | }
56 | }
57 |
58 | const mapDispatchToProps = (dispatch) => {
59 | return {
60 | fetchAllRecipes: () => dispatch(fetchRecipes()),
61 | fetchAuthor: () => dispatch(fetchAuthor())
62 | }
63 | }
64 |
65 | export default connect(mapStateToProps, mapDispatchToProps)(RecipeList)
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/components/RecipeListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const RecipeListItem = ({ name, time}) => {
4 | return ({name} ({time})
);
5 | }
6 |
7 | export default RecipeListItem;
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './components/App';
4 |
5 | import { Provider } from 'react-redux'
6 | import { createStore, compose, applyMiddleware } from 'redux'
7 | import thunkMiddleware from 'redux-thunk';
8 | import reducers from './reducers';
9 |
10 |
11 | // create store and use redux dev tool middleware as browser extension
12 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
13 | const store = createStore(
14 | reducers,
15 | composeEnhancers(
16 | applyMiddleware(thunkMiddleware)
17 | )
18 | );
19 |
20 | ReactDOM.render(
21 |
22 |
23 | ,
24 | document.getElementById('mount')
25 | );
26 |
27 |
28 |
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/reducers/author.js:
--------------------------------------------------------------------------------
1 | // author reducer
2 |
3 | const initialState = {
4 | loading: false,
5 | name: null
6 | }
7 |
8 | export default (state = initialState, action) => {
9 | switch (action.type) {
10 | case 'UPDATE_AUTHOR':
11 | return { ...state, name: action.payload };
12 | case 'REQUEST_AUTHOR':
13 | return { ...state, loading: action.payload.loading };
14 | case 'RECEIVE_AUTHOR':
15 | return { ...state, loading: action.payload.loading, name: action.payload.data };
16 | default:
17 | return state;
18 | }
19 | };
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import recipes from './recipes';
3 | import author from './author';
4 |
5 | const reducers = combineReducers({
6 | recipes,
7 | author
8 | })
9 |
10 | export default reducers;
--------------------------------------------------------------------------------
/12b-data-fetching-redux/src/reducers/recipes.js:
--------------------------------------------------------------------------------
1 | // recipe reducer
2 |
3 | const initialState = {
4 | loading: false,
5 | list: null
6 | }
7 |
8 | export default (state = initialState, action) => {
9 | switch (action.type) {
10 | case 'ADD_RECIPE':
11 | if(!state.list){
12 | return { ...state, list: [ action.payload ] };
13 | }
14 | return { ...state, list: [ ...state.list, action.payload] };
15 | case 'REQUEST_RECIPES':
16 | return { ...state, loading: action.payload.loading };
17 | case 'RECEIVE_RECIPES':
18 | return { ...state, loading: action.payload.loading, list: action.payload.data };
19 | default:
20 | return state;
21 | }
22 | };
--------------------------------------------------------------------------------
/13-react-router/.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 |
--------------------------------------------------------------------------------
/13-react-router/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "13-react-router",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4",
8 | "react-router-dom": "^4.1.1"
9 | },
10 | "devDependencies": {
11 | "react-scripts": "1.0.7"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/13-react-router/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 13 - React Router
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/13-react-router/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route } from 'react-router-dom';
3 | import Home from './Home';
4 | import Header from './Header';
5 | import Recipes from './Recipes';
6 |
7 | const App = () => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
17 | export default App;
--------------------------------------------------------------------------------
/13-react-router/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const Header = () => {
5 | const tabData = [
6 | { text: 'Home', link: '/' },
7 | { text: 'Recipes', link: '/recipes' },
8 | ];
9 | const tabs = tabData.map((tab, index) => ({tab.text}));
10 | return();
11 | }
12 |
13 | export default Header;
--------------------------------------------------------------------------------
/13-react-router/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Home = () => (
4 | Welcome to the Recipe Book as React Router Example
5 | )
6 |
7 | export default Home;
--------------------------------------------------------------------------------
/13-react-router/src/components/RecipeDetail.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const recipeData = {
4 | '1' : { name: 'Nudelsalat', time: '1 Stunde'},
5 | '2' : { name: 'Hackbraten', time: '30 Minuten'}
6 | }
7 |
8 | const RecipeDetail = ({ match, history}) => {
9 | const id = match.params.id
10 | const recipe = recipeData[id];
11 | if(!recipe){
12 | return (No recipe found!
);
13 | }
14 | return (
15 |
16 |
Recipe with ID {id}
17 |
Name: {recipe.name}
18 |
Time: {recipe.time}
19 |
20 |
21 | )
22 | }
23 |
24 | export default RecipeDetail;
25 |
--------------------------------------------------------------------------------
/13-react-router/src/components/RecipeOverview.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | class RecipeOverview extends Component {
5 | render() {
6 | const recipeData = [
7 | { id: 1, name: 'Nudelsalat', time: '1 Stunde'},
8 | { id: 2, name: 'Hackbraten', time: '30 Minuten'}
9 | ];
10 | const recipes = recipeData.map((recipe, index) => (
11 | {recipe.name} ({recipe.time})
12 | ));
13 | return(
14 |
15 |
My Recipe List
16 |
17 |
18 | )
19 | }
20 | }
21 |
22 | export default RecipeOverview;
23 |
--------------------------------------------------------------------------------
/13-react-router/src/components/Recipes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route } from 'react-router-dom';
3 | import RecipeOverview from './RecipeOverview';
4 | import RecipeDetail from './RecipeDetail';
5 |
6 | const Recipes = () => (
7 |
8 |
9 |
10 |
11 | )
12 |
13 | export default Recipes;
--------------------------------------------------------------------------------
/13-react-router/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import App from './components/App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('mount')
11 | );
12 |
13 |
--------------------------------------------------------------------------------
/14-react-build-process/.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 |
--------------------------------------------------------------------------------
/14-react-build-process/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/14-react-build-process/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/14-react-build-process/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right