mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
step 8 exercise GO!
This commit is contained in:
@@ -1,197 +0,0 @@
|
||||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = function(exports) {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
/******/
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = "./step2-01/demo/src/index.tsx");
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
||||
/***/ "./step2-01/demo/src/async/index.ts":
|
||||
/*!******************************************!*\
|
||||
!*** ./step2-01/demo/src/async/index.ts ***!
|
||||
\******************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\nvar __generator = (undefined && undefined.__generator) || function (thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (_) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n};\nfunction fetchSomething() {\n return __awaiter(this, void 0, void 0, function () {\n var response;\n return __generator(this, function (_a) {\n switch (_a.label) {\n case 0: return [4 /*yield*/, fetch('http://localhost:3000/hello')];\n case 1:\n response = _a.sent();\n return [4 /*yield*/, response.text()];\n case 2: return [2 /*return*/, _a.sent()];\n }\n });\n });\n}\n// Async functions always returns Promise\nfetchSomething().then(function (text) {\n console.log('hello ' + text);\n});\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/async/index.ts?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./step2-01/demo/src/generics/index.ts":
|
||||
/*!*********************************************!*\
|
||||
!*** ./step2-01/demo/src/generics/index.ts ***!
|
||||
\*********************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n// Generics for classes\nvar Stack = /** @class */ (function () {\n function Stack() {\n this.data = [];\n }\n Stack.prototype.push = function (item) {\n this.data.push(item);\n };\n Stack.prototype.pop = function () {\n return this.data.pop();\n };\n return Stack;\n}());\nvar numberStack = new Stack();\nvar stringStack = new Stack();\n// Generics for functions\nfunction reverse(arg) {\n // TODO: implement the logic to reverse the array\n return arg;\n}\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/generics/index.ts?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./step2-01/demo/src/index.tsx":
|
||||
/*!*************************************!*\
|
||||
!*** ./step2-01/demo/src/index.tsx ***!
|
||||
\*************************************/
|
||||
/*! no exports provided */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./types */ \"./step2-01/demo/src/types/index.ts\");\n/* harmony import */ var _interfaces__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./interfaces */ \"./step2-01/demo/src/interfaces/index.ts\");\n/* harmony import */ var _modules__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./modules */ \"./step2-01/demo/src/modules/index.ts\");\n/* harmony import */ var _generics__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./generics */ \"./step2-01/demo/src/generics/index.ts\");\n/* harmony import */ var _async__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./async */ \"./step2-01/demo/src/async/index.ts\");\n/* harmony import */ var _spread__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./spread */ \"./step2-01/demo/src/spread/index.ts\");\n// Interesting Typescript Topics\n// types\n\n// interface\n\n// modularity\n\n// generics\n\n// await / async\n\n// spread syntax\n\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/index.tsx?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./step2-01/demo/src/interfaces/index.ts":
|
||||
/*!***********************************************!*\
|
||||
!*** ./step2-01/demo/src/interfaces/index.ts ***!
|
||||
\***********************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\nvar MyCar = /** @class */ (function () {\n function MyCar() {\n }\n return MyCar;\n}());\nvar myCar = {\n make: 'Honda',\n model: 'Accord'\n};\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/interfaces/index.ts?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./step2-01/demo/src/modules/default.ts":
|
||||
/*!**********************************************!*\
|
||||
!*** ./step2-01/demo/src/modules/default.ts ***!
|
||||
\**********************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\nvar DefaultClass = /** @class */ (function () {\n function DefaultClass() {\n this.hello = 'world';\n }\n return DefaultClass;\n}());\n/* harmony default export */ __webpack_exports__[\"default\"] = (DefaultClass);\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/modules/default.ts?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./step2-01/demo/src/modules/index.ts":
|
||||
/*!********************************************!*\
|
||||
!*** ./step2-01/demo/src/modules/index.ts ***!
|
||||
\********************************************/
|
||||
/*! no exports provided */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _named__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./named */ \"./step2-01/demo/src/modules/named.ts\");\n/* harmony import */ var _default__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./default */ \"./step2-01/demo/src/modules/default.ts\");\n\n\n// Print out the exports\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConst\"]);\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConst\"]);\nconsole.log(Object(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedFn\"])());\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedObj\"]);\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConstBracket\"]);\n// Print out exports through module level import\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConst\"]);\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedFn\"]());\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedObj\"]);\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConstBracket\"]);\n\nconsole.log(new _default__WEBPACK_IMPORTED_MODULE_1__[\"default\"]().hello);\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/modules/index.ts?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./step2-01/demo/src/modules/named.ts":
|
||||
/*!********************************************!*\
|
||||
!*** ./step2-01/demo/src/modules/named.ts ***!
|
||||
\********************************************/
|
||||
/*! exports provided: namedConst, namedFn, namedObj, namedConstBracket */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"namedConst\", function() { return namedConst; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"namedFn\", function() { return namedFn; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"namedObj\", function() { return namedObj; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"namedConstBracket\", function() { return namedConstBracket; });\nvar namedConst = 5;\nfunction namedFn() {\n return 5;\n}\nvar namedObj = {\n hello: 'world'\n};\nvar namedConstBracket = 10;\n\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/modules/named.ts?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./step2-01/demo/src/spread/index.ts":
|
||||
/*!*******************************************!*\
|
||||
!*** ./step2-01/demo/src/spread/index.ts ***!
|
||||
\*******************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\nvar __assign = (undefined && undefined.__assign) || function () {\n __assign = Object.assign || function(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\n t[p] = s[p];\n }\n return t;\n };\n return __assign.apply(this, arguments);\n};\n// Destructuring\nvar _a = [1, 2, 3, 4], a = _a[0], b = _a[1], rest = _a.slice(2);\nconsole.log(a, b, rest); // 1,2,[3,4]\n// Array assignment\nvar list = [1, 2];\nlist = list.concat([3, 4]);\nconsole.log(list); // [1,2,3,4]\n// Object assignment\nvar point2D = { x: 1, y: 2 };\nvar point3D = __assign({}, point2D, { z: 3 });\n// Concat two objects\nvar obj1 = { x: 1 };\nvar obj2 = { y: 2 };\nvar obj3 = __assign({}, obj1, obj2);\n// Destructuring object\nvar x = obj3.x;\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/spread/index.ts?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./step2-01/demo/src/types/index.ts":
|
||||
/*!******************************************!*\
|
||||
!*** ./step2-01/demo/src/types/index.ts ***!
|
||||
\******************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
eval("__webpack_require__.r(__webpack_exports__);\nvar __extends = (undefined && undefined.__extends) || (function () {\n var extendStatics = function (d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };\n return extendStatics(d, b);\n };\n return function (d, b) {\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n };\n})();\n// Basic Types\nvar isDone = false;\nvar decimal = 6;\nvar color = 'blue';\nvar sky = \"the sky is \" + color;\n// casting\nvar choose1 = { common: '5' };\n// Classes\nvar Animal = /** @class */ (function () {\n function Animal() {\n }\n return Animal;\n}());\n// Illustration purposes only\n// In real apps, avoid inheritance if possible\n// noted exception: React.Component with react@<16.8.0\nvar Cat = /** @class */ (function (_super) {\n __extends(Cat, _super);\n function Cat() {\n return _super !== null && _super.apply(this, arguments) || this;\n }\n return Cat;\n}(Animal));\nvar Dog = /** @class */ (function (_super) {\n __extends(Dog, _super);\n function Dog() {\n return _super !== null && _super.apply(this, arguments) || this;\n }\n return Dog;\n}(Animal));\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/types/index.ts?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
||||
@@ -32,3 +32,5 @@ log('hello world');
|
||||
// Place your code for the async / await exercise here
|
||||
// ...
|
||||
})();
|
||||
|
||||
export default {};
|
||||
|
||||
@@ -1,3 +1,31 @@
|
||||
# Step 2.8
|
||||
|
||||
Combine Reducers
|
||||
|
||||
This lesson is just a helper to make the process of writing reducers use less boilerplate code. This step briefly introduces to a world of helpers in writing Redux code.
|
||||
|
||||
# Take a peek at useful helpers and middleware created by community are:
|
||||
|
||||
- immer: https://github.com/mweststrate/immer - improves ergonomics of working with immutables by introducing the concept of mutating a draft
|
||||
|
||||
- redux-starter-kit: https://github.com/reduxjs/redux-starter-kit - help address common concerns of Redux in boilerplate and complexity
|
||||
|
||||
# Exercise
|
||||
|
||||
1. open up `exercise/src/reducers/index.ts`
|
||||
|
||||
2. implement the `filterReducer` function with a switch / case statement - it is contrived to have a switch case for ONE condition, but serves to be a good exercise here
|
||||
|
||||
3. replace the export reducer function with the help of the `combineReducer()` function from `redux`
|
||||
|
||||
# Bonus Exercise
|
||||
|
||||
The Redux team came up with `redux-starter-kit` to address a lot of boilerplate concerns. They also embed the immer library to make it nicer to write reducer functions. So, let's try out `immer`! Look at this example: https://github.com/mweststrate/immer#reducer-example
|
||||
|
||||
1. import immer into the `exercise/src/reducers/pureFunction.ts` file
|
||||
|
||||
2. replace the implementation of the pure functions with the help of immer's `produce()`
|
||||
|
||||
3. run `npm test` in the root folder to see if it still works!
|
||||
|
||||
4. look at the web app to make sure it still works!
|
||||
|
||||
6
step2-08/exercise/index.html
Normal file
6
step2-08/exercise/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
9
step2-08/exercise/src/actions/index.ts
Normal file
9
step2-08/exercise/src/actions/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import uuid from 'uuid/v4';
|
||||
|
||||
export const actions = {
|
||||
addTodo: (label: string) => ({ type: 'addTodo', id: uuid(), label }),
|
||||
remove: (id: string) => ({ type: 'remove', id }),
|
||||
complete: (id: string) => ({ type: 'complete', id }),
|
||||
clear: () => ({ type: 'clear' }),
|
||||
setFilter: (filter: string) => ({ type: 'setFilter', filter })
|
||||
};
|
||||
26
step2-08/exercise/src/components/TodoApp.tsx
Normal file
26
step2-08/exercise/src/components/TodoApp.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Stack, Customizer, mergeStyles, getTheme } from 'office-ui-fabric-react';
|
||||
import { TodoFooter } from './TodoFooter';
|
||||
import { TodoHeader } from './TodoHeader';
|
||||
import { TodoList } from './TodoList';
|
||||
import { Store } from '../store';
|
||||
import { FluentCustomizations } from '@uifabric/fluent-theme';
|
||||
|
||||
const className = mergeStyles({
|
||||
padding: 25,
|
||||
...getTheme().effects.elevation4
|
||||
});
|
||||
|
||||
export const TodoApp = () => {
|
||||
return (
|
||||
<Customizer {...FluentCustomizations}>
|
||||
<Stack horizontalAlign="center">
|
||||
<Stack style={{ width: 400 }} gap={25} className={className}>
|
||||
<TodoHeader />
|
||||
<TodoList />
|
||||
<TodoFooter />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Customizer>
|
||||
);
|
||||
};
|
||||
42
step2-08/exercise/src/components/TodoFooter.tsx
Normal file
42
step2-08/exercise/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { Text } from '@uifabric/experiments';
|
||||
import { Stack } from 'office-ui-fabric-react';
|
||||
import { Store } from '../store';
|
||||
import { DefaultButton } from 'office-ui-fabric-react';
|
||||
import { connect } from 'react-redux';
|
||||
import { actions } from '../actions';
|
||||
|
||||
interface TodoFooterProps {
|
||||
clear: () => void;
|
||||
todos: Store['todos'];
|
||||
}
|
||||
|
||||
const TodoFooter = (props: TodoFooterProps) => {
|
||||
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||
|
||||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state: Store) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any) {
|
||||
return {
|
||||
clear: () => dispatch(actions.clear())
|
||||
};
|
||||
}
|
||||
|
||||
const component = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TodoFooter);
|
||||
|
||||
export { component as TodoFooter };
|
||||
78
step2-08/exercise/src/components/TodoHeader.tsx
Normal file
78
step2-08/exercise/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { Text } from '@uifabric/experiments';
|
||||
import { Stack } from 'office-ui-fabric-react';
|
||||
import { Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react';
|
||||
import { FilterTypes, Store } from '../store';
|
||||
import { actions } from '../actions';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface TodoHeaderProps {
|
||||
addTodo: (label: string) => void;
|
||||
setFilter: (filter: FilterTypes) => void;
|
||||
filter: FilterTypes;
|
||||
}
|
||||
|
||||
interface TodoHeaderState {
|
||||
labelInput: string;
|
||||
}
|
||||
|
||||
class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
|
||||
constructor(props: TodoHeaderProps) {
|
||||
super(props);
|
||||
this.state = { labelInput: undefined };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Stack gap={10}>
|
||||
<Stack horizontal horizontalAlign="center">
|
||||
<Text variant="xxLarge">todos</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal gap={10}>
|
||||
<Stack.Item grow>
|
||||
<TextField placeholder="What needs to be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||
</Stack.Item>
|
||||
<PrimaryButton onClick={this.onAdd}>Add</PrimaryButton>
|
||||
</Stack>
|
||||
|
||||
<Pivot onLinkClick={this.onFilter}>
|
||||
<PivotItem headerText="all" />
|
||||
<PivotItem headerText="active" />
|
||||
<PivotItem headerText="completed" />
|
||||
</Pivot>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
private onAdd = () => {
|
||||
this.props.addTodo(this.state.labelInput);
|
||||
this.setState({ labelInput: undefined });
|
||||
};
|
||||
|
||||
private onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||
this.setState({ labelInput: newValue });
|
||||
};
|
||||
|
||||
private onFilter = (item: PivotItem) => {
|
||||
this.props.setFilter(item.props.headerText as FilterTypes);
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: Store) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any) {
|
||||
return {
|
||||
addTodo: (label: string) => dispatch(actions.addTodo(label)),
|
||||
setFilter: (filter: FilterTypes) => dispatch(actions.setFilter(filter))
|
||||
};
|
||||
}
|
||||
|
||||
const component = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TodoHeader);
|
||||
|
||||
export { component as TodoHeader };
|
||||
40
step2-08/exercise/src/components/TodoList.tsx
Normal file
40
step2-08/exercise/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { Stack } from 'office-ui-fabric-react';
|
||||
import { TodoListItem } from './TodoListItem';
|
||||
import { Store, FilterTypes } from '../store';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface TodoListProps {
|
||||
todos: Store['todos'];
|
||||
filter: FilterTypes;
|
||||
}
|
||||
|
||||
const TodoList = (props: TodoListProps) => {
|
||||
const { filter, todos } = props;
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack gap={10}>
|
||||
{filteredTodos.map(id => (
|
||||
<TodoListItem key={id} id={id} />
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state: Store) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const component = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TodoList);
|
||||
|
||||
export { component as TodoList };
|
||||
48
step2-08/exercise/src/components/TodoListItem.tsx
Normal file
48
step2-08/exercise/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Stack, Checkbox, IconButton } from 'office-ui-fabric-react';
|
||||
import { Store } from '../store';
|
||||
import { connect } from 'react-redux';
|
||||
import { actions } from '../actions';
|
||||
|
||||
interface TodoListItemProps {
|
||||
id: string;
|
||||
todos: Store['todos'];
|
||||
remove: (id: string) => void;
|
||||
complete: (id: string) => void;
|
||||
}
|
||||
|
||||
class TodoListItem extends React.Component<TodoListItemProps, {}> {
|
||||
render() {
|
||||
const { todos, id, complete, remove } = this.props;
|
||||
const item = todos[id];
|
||||
|
||||
return (
|
||||
<Stack horizontal verticalAlign="center" horizontalAlign="space-between">
|
||||
<Checkbox label={item.label} checked={item.completed} onChange={() => complete(id)} />
|
||||
<div>
|
||||
<IconButton iconProps={{ iconName: 'Cancel' }} onClick={() => remove(id)} />
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps({ todos }: Store) {
|
||||
return {
|
||||
todos
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any) {
|
||||
return {
|
||||
remove: (id: string) => dispatch(actions.remove(id)),
|
||||
complete: (id: string) => dispatch(actions.complete(id))
|
||||
};
|
||||
}
|
||||
|
||||
const component = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TodoListItem);
|
||||
|
||||
export { component as TodoListItem };
|
||||
30
step2-08/exercise/src/index.tsx
Normal file
30
step2-08/exercise/src/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { reducer } from './reducers';
|
||||
import { createStore, compose } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import { TodoApp } from './components/TodoApp';
|
||||
import { actions } from './actions';
|
||||
import { initializeIcons } from '@uifabric/icons';
|
||||
import { Store } from './store';
|
||||
|
||||
/* Goop for making the Redux dev tool to work */
|
||||
declare var window: any;
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
function createStoreWithDevTool(reducer, initialStore?: Store) {
|
||||
return createStore(reducer, initialStore, composeEnhancers());
|
||||
}
|
||||
|
||||
const store = createStoreWithDevTool(reducer);
|
||||
|
||||
store.dispatch(actions.addTodo('hello'));
|
||||
store.dispatch(actions.addTodo('world'));
|
||||
|
||||
initializeIcons();
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<TodoApp />
|
||||
</Provider>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
35
step2-08/exercise/src/reducers/index.ts
Normal file
35
step2-08/exercise/src/reducers/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Store } from '../store';
|
||||
import { addTodo, remove, complete, clear, setFilter } from './pureFunctions';
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
function todoReducer(state: Store['todos'] = {}, action: any): Store['todos'] {
|
||||
switch (action.type) {
|
||||
case 'addTodo':
|
||||
return addTodo(state, action.id, action.label);
|
||||
|
||||
case 'remove':
|
||||
return remove(state, action.id);
|
||||
|
||||
case 'clear':
|
||||
return clear(state);
|
||||
|
||||
case 'complete':
|
||||
return complete(state, action.id);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function filterReducer(state: Store['filter'] = 'all', action: any): Store['filter'] {
|
||||
// TODO: fill in the blank here with a switch / case statement to return new filter state as specified in `action.filter` message
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// TODO: rewrite this reducer function with combineReducer() helper
|
||||
export function reducer(state: Store, action: any): Store {
|
||||
return {
|
||||
todos: todoReducer(state.todos, action),
|
||||
filter: 'all'
|
||||
};
|
||||
}
|
||||
29
step2-08/exercise/src/reducers/pureFunctions.spec.ts
Normal file
29
step2-08/exercise/src/reducers/pureFunctions.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { addTodo, complete } from './pureFunctions';
|
||||
import { Store } from '../store';
|
||||
|
||||
describe('TodoApp reducers', () => {
|
||||
it('can add an item', () => {
|
||||
const state = <Store['todos']>{};
|
||||
|
||||
const newState = addTodo(state, '0', 'item1');
|
||||
|
||||
const keys = Object.keys(newState);
|
||||
|
||||
expect(newState).not.toBe(state);
|
||||
expect(keys.length).toBe(1);
|
||||
expect(newState[keys[0]].label).toBe('item1');
|
||||
expect(newState[keys[0]].completed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('can complete an item', () => {
|
||||
const state = <Store['todos']>{};
|
||||
|
||||
let newState = addTodo(state, '0', 'item1');
|
||||
|
||||
const key = Object.keys(newState)[0];
|
||||
|
||||
newState = complete(newState, key);
|
||||
|
||||
expect(newState[key].completed).toBeTruthy();
|
||||
});
|
||||
});
|
||||
36
step2-08/exercise/src/reducers/pureFunctions.ts
Normal file
36
step2-08/exercise/src/reducers/pureFunctions.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Store, FilterTypes } from '../store';
|
||||
|
||||
export function addTodo(state: Store['todos'], id: string, label: string): Store['todos'] {
|
||||
return { ...state, [id]: { label, completed: false } };
|
||||
}
|
||||
|
||||
export function remove(state: Store['todos'], id: string) {
|
||||
const newTodos = { ...state };
|
||||
|
||||
delete newTodos[id];
|
||||
|
||||
return newTodos;
|
||||
}
|
||||
|
||||
export function complete(state: Store['todos'], id: string) {
|
||||
const newTodos = { ...state };
|
||||
newTodos[id].completed = !newTodos[id].completed;
|
||||
|
||||
return newTodos;
|
||||
}
|
||||
|
||||
export function clear(state: Store['todos']) {
|
||||
const newTodos = { ...state };
|
||||
|
||||
Object.keys(state.todos).forEach(key => {
|
||||
if (state.todos[key].completed) {
|
||||
delete newTodos[key];
|
||||
}
|
||||
});
|
||||
|
||||
return newTodos;
|
||||
}
|
||||
|
||||
export function setFilter(state: Store['filter'], filter: FilterTypes) {
|
||||
return filter;
|
||||
}
|
||||
14
step2-08/exercise/src/store/index.ts
Normal file
14
step2-08/exercise/src/store/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||
|
||||
export interface TodoItem {
|
||||
label: string;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
export interface Store {
|
||||
todos: {
|
||||
[id: string]: TodoItem;
|
||||
};
|
||||
|
||||
filter: FilterTypes;
|
||||
}
|
||||
Reference in New Issue
Block a user