├── Chapter01 ├── package-lock.json └── package.json ├── Chapter02 ├── package-lock.json ├── package.json └── src │ ├── arrow_functions.js │ ├── class_extdate.js │ ├── class_persons.js │ ├── default_arguments.js │ ├── functional_code.js │ ├── get_service_with_async_await.js │ ├── get_service_with_promises.js │ ├── iife_counter.js │ ├── let_const.js │ ├── map_filter_reduce.js │ ├── module_conversion_usage.js │ ├── module_conversions.js │ ├── module_counter.1.js │ ├── module_counter.2.js │ ├── module_counter_usage.js │ ├── opaque_types.js │ ├── opaque_usage.js │ ├── spread_and_rest.js │ ├── test_for_chrome │ ├── module_counter.SIMPLE.js │ └── module_counter_usage.SIMPLE.html │ ├── types_advanced.js │ └── types_basic.js ├── Chapter03 ├── package-lock.json ├── package.json └── src │ ├── dbaccess.js │ ├── doroundmath.js │ ├── flowcomments.js │ ├── miniserver.js │ ├── process_exec.js │ ├── process_fork.js │ ├── process_fork_dir.js │ ├── process_request.js │ ├── process_spawn.js │ ├── process_spawn_dir.js │ ├── promisify.js │ ├── roundmath.js │ ├── zip_files.js │ └── zip_send.js ├── Chapter04 ├── flags │ ├── america │ │ ├── north │ │ │ ├── CA.png │ │ │ ├── MX.png │ │ │ └── US.png │ │ └── south │ │ │ ├── AR.png │ │ │ ├── BR.png │ │ │ ├── PY.png │ │ │ └── UY.png │ ├── europe │ │ ├── DE.png │ │ ├── ES.png │ │ ├── FR.png │ │ ├── GB.png │ │ ├── IT.png │ │ └── PT.png │ └── license.txt ├── package-lock.json ├── package.json └── src │ ├── cors_request.html │ ├── cors_server.js │ ├── get_parameters.js │ ├── hello_world.js │ ├── helmet_world.js │ ├── http_server.js │ ├── https_server.js │ ├── jwt_server.js │ ├── middleware.js │ ├── restful_cities.js │ ├── restful_countries.js │ ├── restful_db.js │ ├── restful_regions.js │ ├── restful_server.js │ ├── restful_server_cors.js │ ├── router_cities.js │ ├── router_countries.js │ ├── router_home.js │ ├── router_regions.js │ ├── routing.js │ ├── serve_helmet.js │ ├── serve_statics.js │ ├── serve_statics_alt.js │ └── validate_user.js ├── Chapter05 ├── coverage │ ├── clover.xml │ ├── coverage-final.json │ ├── lcov-report │ │ ├── base.css │ │ ├── block-navigation.js │ │ ├── index.html │ │ ├── prettify.css │ │ ├── prettify.js │ │ ├── restful_regions.js.html │ │ ├── roundmath.js.html │ │ ├── sort-arrow-sprite.png │ │ ├── sorter.js │ │ └── validate_user.js.html │ └── lcov.info ├── package-lock.json ├── package.json ├── postman_collection.json ├── serv_error.json.log ├── serv_error.txt.log ├── src │ ├── morgan_in_winston_server.js │ ├── morgan_server.1.js │ ├── morgan_server.js │ ├── restful_cities.js │ ├── restful_countries.js │ ├── restful_db.js │ ├── restful_regions.js │ ├── restful_regions.test.js │ ├── restful_server.js │ ├── roundmath.js │ ├── roundmath.test.js │ ├── show_env.js │ ├── validate_user.js │ ├── validate_user.test.js │ └── winston_server.js ├── swagger.json └── swagger.yaml ├── Chapter06 ├── README.md ├── config-overrides.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── App.css │ ├── App.js │ ├── components │ ├── countryFilterBar │ │ ├── countryFilterBar.js │ │ ├── countryFilterBar.md │ │ ├── countryFilterBar.old.style.js │ │ ├── countryFilterBar.story.js │ │ ├── countryFilterBarWithAddons.story.js │ │ └── index.js │ ├── expandableCard.1 │ │ └── index.js │ ├── expandableCard.2 │ │ ├── expandableCard.css │ │ ├── expandableCard.story.js │ │ ├── expandableCardWithAddon.story.js │ │ └── index.js │ ├── general.css │ ├── regionsInformationTable │ │ └── index.js │ ├── resultsDataTable.1 │ │ └── index.js │ └── resultsDataTable.2 │ │ └── index.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── registerServiceWorker.js ├── Chapter07 ├── README.md ├── config-overrides.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── App.1.js │ ├── App.2.js │ ├── App.3.js │ ├── App.4.js │ ├── components │ ├── i18nform │ │ ├── i18n.js │ │ ├── i18nform.a11y.js │ │ ├── i18nform.js │ │ ├── i18nform.story.js │ │ ├── index.js │ │ ├── styles.css │ │ ├── styles.scss │ │ ├── translations.en.json │ │ └── translations.es.json │ ├── sassButton │ │ ├── _constants.scss │ │ ├── _mixins.scss │ │ ├── index.js │ │ ├── sassButton.a11y.js │ │ ├── sassButton.js │ │ ├── sassButton.story.js │ │ ├── styles.css │ │ └── styles.scss │ └── styledButton │ │ ├── constants.js │ │ ├── index.js │ │ ├── styledButton.a11y.js │ │ ├── styledButton.js │ │ └── styledButton.story.js │ ├── index.js │ ├── logo.svg │ ├── makeResize.js │ ├── registerServiceWorker.js │ └── utilities │ ├── index.js │ └── keyToClick.js ├── Chapter08 ├── README.md ├── config-overrides.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── App.counter.js │ ├── App.regions.functional.js │ ├── App.regions.js │ ├── App.routing.auth.js │ ├── App.routing.js │ ├── App.routing.protected.js │ ├── App.splitting.js │ ├── counterApp │ ├── clicksDisplay.component.js │ ├── clicksDisplay.connected.js │ ├── counter.actions.js │ ├── counter.component.js │ ├── counter.connected.js │ ├── counter.reducer.js │ ├── index.js │ └── store.js │ ├── general.css │ ├── index.js │ ├── regionsApp │ ├── countrySelect.component.js │ ├── countrySelect.connected.js │ ├── index.js │ ├── regionsTable.component.js │ ├── regionsTable.connected.js │ ├── serviceApi.js │ ├── store.js │ ├── world.actions.js │ └── world.reducer.js │ ├── registerServiceWorker.js │ ├── routingApp │ ├── authRoute.component.js │ ├── authRoute.connected.js │ ├── index.js │ ├── login.actions.js │ ├── login.component.js │ ├── login.connected.js │ ├── login.reducer.js │ ├── protectedRoutes.component.js │ ├── protectedRoutes.connected.js │ ├── serviceApi.js │ └── store.js │ └── splittingApp │ ├── alpha.component.js │ ├── alpha.loadable.js │ ├── bravo.component.js │ ├── bravo.loadable.js │ ├── charlie.component.js │ ├── charlie.loadable.js │ ├── help.component.js │ ├── help.loadable.js │ ├── index.js │ ├── loadingStatus.component.js │ ├── zulu.component.js │ └── zulu.loadable.js ├── Chapter09-10 ├── README.md ├── config-overrides.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── App.counter.js │ ├── App.regions.js │ ├── App.routing.auth.js │ ├── counterApp │ ├── __snapshots__ │ │ ├── clicksDisplay.test.js.snap │ │ └── counter.test.js.snap │ ├── clicksDisplay.component.js │ ├── clicksDisplay.connected.js │ ├── clicksDisplay.test.js │ ├── counter.actions.js │ ├── counter.component.js │ ├── counter.connected.js │ ├── counter.reducer.js │ ├── counter.test.js │ ├── index.js │ └── store.js │ ├── general.css │ ├── index.js │ ├── index.without.logging.js │ ├── logging │ ├── index.js │ └── index.without.level.js │ ├── regionsApp │ ├── __snapshots__ │ │ ├── countryAndRegions.test.js.snap │ │ ├── countrySelect.snapshot.test.js.snap │ │ └── regionsTable.snapshot.test.js.snap │ ├── countryAndRegions.test.js │ ├── countrySelect.component.js │ ├── countrySelect.connected.js │ ├── countrySelect.snapshot.test.js │ ├── countrySelect.test.js │ ├── index.js │ ├── regionsTable.component.js │ ├── regionsTable.connected.js │ ├── regionsTable.connected.test.js │ ├── regionsTable.snapshot.test.js │ ├── regionsTable.test.js │ ├── serviceApi.js │ ├── store.js │ ├── world.actions.js │ ├── world.actions.test.js │ ├── world.reducer.js │ └── world.reducer.test.js │ ├── registerServiceWorker.js │ └── routingApp │ ├── authRoute.component.js │ ├── authRoute.connected.js │ ├── index.js │ ├── login.actions.js │ ├── login.component.js │ ├── login.connected.js │ ├── login.reducer.js │ ├── protectedRoutes.component.js │ ├── protectedRoutes.connected.js │ ├── serviceApi.js │ └── store.js ├── Chapter11 ├── App.adaptive.js ├── App.js ├── App.original.fixed.js ├── App.original.js ├── App.regions.js ├── App.routing.js ├── App.styled.js ├── App.test.js ├── README.md ├── app.json ├── devices.csv ├── package-lock.json ├── package.json └── src │ ├── adaptiveApp │ ├── actions.js │ ├── adaptiveView.component.js │ ├── adaptiveView.connected.js │ ├── device.js │ ├── deviceHandler.component.js │ ├── deviceHandler.connected.js │ ├── main.js │ ├── reducer.js │ └── store.js │ ├── regionsApp │ ├── countrySelect.component.js │ ├── countrySelect.connected.js │ ├── index.js │ ├── main.js │ ├── regionsTable.component.js │ ├── regionsTable.connected.js │ ├── serviceApi.js │ ├── store.js │ ├── world.actions.js │ └── world.reducer.js │ ├── regionsStyledApp │ ├── countrySelect.component.js │ ├── countrySelect.connected.js │ ├── device.js │ ├── deviceHandler.component.js │ ├── deviceHandler.connected.js │ ├── index.js │ ├── main.component.js │ ├── main.connected.js │ ├── regionsTable.component.js │ ├── regionsTable.connected.js │ ├── serviceApi.js │ ├── store.js │ ├── styleConstants.js │ ├── world.actions.js │ └── world.reducer.js │ └── routingApp │ ├── drawer.js │ ├── hamburger.png │ └── screens.js ├── Chapter12 ├── App.js ├── App.reactotron.js ├── App.standard.js ├── App.storybook.js ├── App.test.js ├── README.md ├── app.json ├── package-lock.json ├── package.json ├── reactotronConfig.js ├── src │ └── regionsStyledApp │ │ ├── __snapshots__ │ │ ├── countrySelect.snapshot.test.js.snap │ │ ├── main.snapshot.test.js.snap │ │ └── regionsTable.snapshot.test.js.snap │ │ ├── countrySelect.component.js │ │ ├── countrySelect.connected.js │ │ ├── countrySelect.snapshot.test.js │ │ ├── countrySelect.story.js │ │ ├── countrySelect.test.js │ │ ├── device.js │ │ ├── deviceHandler.component.js │ │ ├── deviceHandler.connected.js │ │ ├── index.js │ │ ├── main.component.js │ │ ├── main.connected.js │ │ ├── main.snapshot.test.js │ │ ├── regionsTable.component.js │ │ ├── regionsTable.connected.js │ │ ├── regionsTable.connected.test.js │ │ ├── regionsTable.snapshot.test.js │ │ ├── regionsTable.story.js │ │ ├── regionsTable.test.js │ │ ├── serviceApi.js │ │ ├── store.js │ │ ├── store.reactotron.js │ │ ├── styleConstants.js │ │ ├── world.actions.js │ │ ├── world.actions.test.js │ │ ├── world.reducer.js │ │ └── world.reducer.test.js └── storybook │ ├── addons.js │ ├── centered.js │ ├── index.js │ └── storyLoader.js ├── Chapter13 ├── README.md ├── canada_regions.txt ├── config-overrides.js ├── electron-start.for.builder.js ├── electron-start.js ├── electron-start.with.debugging.js ├── electron-start.without.debugging.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ └── r_icon.png ├── src │ ├── App.css │ ├── App.js │ ├── App.regions.js │ ├── App.test.js │ ├── general.css │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── regionsApp │ │ ├── countrySelect.component.js │ │ ├── countrySelect.connected.js │ │ ├── flag-hungary.png │ │ ├── flag-uruguay.png │ │ ├── index.js │ │ ├── r_icon.png │ │ ├── regionsTable.component.js │ │ ├── regionsTable.connected.js │ │ ├── regionsTableWithSave.component.js │ │ ├── regionsTableWithSave.connected.js │ │ ├── seeDirectory.js │ │ ├── serviceApi.js │ │ ├── store.js │ │ ├── store.with.redux.devtools.js │ │ ├── store.without.redux.devtools.js │ │ ├── world.actions.js │ │ └── world.reducer.js │ └── registerServiceWorker.js └── uruguay_regions.txt ├── LICENSE ├── README.md ├── certificates ├── modernjsbook.crt ├── modernjsbook.csr └── modernjsbook.key ├── fixwatcherlimit └── geonames ├── cities.csv ├── countries.csv ├── regions.csv └── world_db_dump.sql /Chapter02/src/arrow_functions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable no-unused-vars, one-var */ 3 | 4 | function Show(value: mixed): void { 5 | this.saved = value; 6 | setTimeout(function() { 7 | console.log(this.saved); 8 | }, 1000); 9 | } 10 | 11 | const w = new Show("Doesn't work..."); // instead, "undefined" is shown 12 | 13 | function Show1(value: mixed): void { 14 | this.saved = value; 15 | setTimeout( 16 | function() { 17 | console.log(this.saved); 18 | }.bind(this), 19 | 1000 20 | ); 21 | } 22 | 23 | function Show2(value: mixed): void { 24 | this.saved = value; 25 | const that = this; 26 | setTimeout(function() { 27 | console.log(that.saved); 28 | }, 2000); 29 | } 30 | 31 | function Show3(value: mixed): void { 32 | this.saved = value; 33 | setTimeout(() => { 34 | console.log(this.saved); 35 | }, 3000); 36 | } 37 | 38 | const x = new Show1("This"); 39 | const y = new Show2("always"); 40 | const z = new Show3("works"); 41 | -------------------------------------------------------------------------------- /Chapter02/src/class_extdate.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | class ExtDate extends Date { 4 | fullDate() { 5 | const months = [ 6 | "JAN", 7 | "FEB", 8 | "MAR", 9 | "APR", 10 | "MAY", 11 | "JUN", 12 | "JUL", 13 | "AUG", 14 | "SEP", 15 | "OCT", 16 | "NOV", 17 | "DEC" 18 | ]; 19 | 20 | return ( 21 | months[this.getMonth()] + 22 | " " + 23 | String(this.getDate()).padStart(2, "0") + 24 | " " + 25 | this.getFullYear() 26 | ); 27 | } 28 | 29 | static getMonthName(m) { 30 | const months = [ 31 | "JAN", 32 | "FEB", 33 | "MAR", 34 | "APR", 35 | "MAY", 36 | "JUN", 37 | "JUL", 38 | "AUG", 39 | "SEP", 40 | "OCT", 41 | "NOV", 42 | "DEC" 43 | ]; 44 | return months[m]; 45 | } 46 | 47 | fullDate2() { 48 | return ( 49 | ExtDate.getMonthName(this.getMonth()) + 50 | " " + 51 | String(this.getDate()).padStart(2, "0") + 52 | " " + 53 | this.getFullYear() 54 | ); 55 | } 56 | } 57 | 58 | console.log(new ExtDate().fullDate()); // "MAY 01 2018" 59 | 60 | console.log(ExtDate.getMonthName(8)); // "SEP" 61 | console.log(new ExtDate().fullDate2()); // "MAY 01 2018" 62 | -------------------------------------------------------------------------------- /Chapter02/src/default_arguments.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | function root(a: number, n: number = 2): number { 4 | return a ** (1 / n); 5 | } 6 | 7 | console.log(root(125, 3)); // 5 8 | console.log(root(4)); // 2 9 | console.log(root(9, undefined)); // 3 10 | 11 | const nthRoot = (a: number, n: number = 2): number => a ** (1 / n); 12 | 13 | console.log(nthRoot(64)); // 8 14 | 15 | class Counter { 16 | count: number; // required by Flow 17 | 18 | constructor(i: number = 0) { 19 | this.count = i; 20 | } 21 | 22 | inc(n: number = 1) { 23 | this.count += n; 24 | } 25 | } 26 | 27 | const cnt = new Counter(); 28 | cnt.inc(3); 29 | cnt.inc(); 30 | cnt.inc(); 31 | console.log(cnt.count); // 5 32 | 33 | function nonsense(a = 2, b = a + 1, c = a * b, d = 9) { 34 | console.log(a, b, c, d); 35 | } 36 | 37 | nonsense(1, 2, 3, 4); // 1 2 3 4 38 | nonsense(); // 2 3 6 9 39 | nonsense(undefined, 4, undefined, 6); // 2 4 8 6 40 | -------------------------------------------------------------------------------- /Chapter02/src/functional_code.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | type voidFunction = (...args: Array) => void; 4 | 5 | const once = (fn: voidFunction): voidFunction => { 6 | let done = false; 7 | return (...args) => { 8 | if (!done) { 9 | done = true; 10 | fn(...args); 11 | } 12 | }; 13 | }; 14 | 15 | const sayHello = () => { 16 | console.log("Hello!"); 17 | }; 18 | 19 | sayHello(); // Hello! 20 | sayHello(); // Hello! 21 | sayHello(); // Hello! 22 | 23 | const sayHelloOnce = once(sayHello); 24 | 25 | sayHelloOnce(); // Hello! 26 | sayHelloOnce(); // (no output) 27 | sayHelloOnce(); // (no output) 28 | -------------------------------------------------------------------------------- /Chapter02/src/iife_counter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /* 4 | In the following code, the only thing that needs 5 | an explicit type declaration for Flow, is "name". 6 | Flow can work out on its own the rest of the types. 7 | */ 8 | 9 | const myCounter = ((name: string) => { 10 | let count = 0; 11 | 12 | const get = () => count; // private 13 | 14 | const inc = () => ++count; 15 | 16 | const toString = () => `${name}: ${get()}`; 17 | 18 | return { 19 | inc, 20 | toString 21 | }; 22 | })("Clicks"); 23 | 24 | console.log(myCounter); // an object with "inc" and "toString" properties 25 | 26 | myCounter.inc(); // 1 27 | myCounter.inc(); // 2 28 | myCounter.inc(); // 3 29 | 30 | myCounter.toString(); // "Clicks: 3" 31 | -------------------------------------------------------------------------------- /Chapter02/src/let_const.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable no-unused-vars, no-constant-condition, no-var, prefer-const */ 3 | 4 | { 5 | let w = 0; 6 | } 7 | console.log(w); // error: w is not defined! 8 | 9 | let x = 1; 10 | { 11 | let x = 99; 12 | } 13 | console.log(x); // still 1; 14 | 15 | let y = 2; 16 | for (let y = 999; false; ) { 17 | /* nothing! */ 18 | } 19 | console.log(y); // still 2; 20 | 21 | const z = 3; 22 | z = 999; // error! 23 | 24 | // Countdown to zero? 25 | var delay = 0; 26 | for (var i = 10; i >= 0; i--) { 27 | delay += 1000; 28 | setTimeout(() => { 29 | console.log(i + (i > 0 ? "..." : "!")); 30 | }, delay); 31 | } 32 | 33 | delay = 0; 34 | for (let i = 10; i >= 0; i--) { 35 | // minimal fix! 36 | delay += 1000; 37 | setTimeout(() => { 38 | console.log(i + (i > 0 ? "..." : "!")); 39 | }, delay); 40 | } 41 | -------------------------------------------------------------------------------- /Chapter02/src/map_filter_reduce.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable no-unused-vars, one-var */ 3 | 4 | const someArray: Array = [22, 9, 60, 12, 4, 56]; 5 | const totalSum = someArray.reduce( 6 | (acc: number, val: number) => acc + val, 7 | 0 8 | ); // 163 9 | 10 | const names = ["Juan", "María", "Sylvia", "Federico"]; 11 | const bulletedList = 12 | ""; 15 | // 16 | 17 | type person = { name: string, sex: string, age: number }; 18 | const family: Array = [ 19 | { name: "Huey", sex: "M", age: 7 }, 20 | { name: "Dewey", sex: "M", age: 8 }, 21 | { name: "Louie", sex: "M", age: 9 }, 22 | { name: "Daisy", sex: "F", age: 25 }, 23 | { name: "Donald", sex: "M", age: 30 }, 24 | { name: "Della", sex: "F", age: 30 } 25 | ]; 26 | 27 | const ages = family.map(x => x.age); 28 | // [7, 8, 9, 25, 30, 30] 29 | 30 | const males = family.filter(x => x.sex === "M"); 31 | console.log(males.length); // 4 32 | 33 | const eldestMaleAge = family 34 | .filter(x => x.sex === "M") 35 | .map(x => x.age) 36 | .reduce((acc, val) => Math.max(acc, val), 0); 37 | console.log(eldestMaleAge); // 30 38 | -------------------------------------------------------------------------------- /Chapter02/src/module_conversion_usage.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | milesToKm, 5 | ouncesToGrams, 6 | poundsToKg as p_to_kg 7 | } from "./module_conversions.js"; 8 | 9 | console.log(`A miss is as good as ${milesToKm(1)} kilometers.`); 10 | 11 | console.log( 12 | `${ouncesToGrams(1)} grams of protection `, 13 | `are worth ${p_to_kg(1) * 1000} grams of cure.` 14 | ); 15 | -------------------------------------------------------------------------------- /Chapter02/src/module_conversions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | type conversion = number => number; 4 | 5 | const SPEED_OF_LIGHT_IN_VACUUM_IN_MPS = 186282; 6 | const KILOMETERS_PER_MILE = 1.60934; 7 | const GRAMS_PER_POUND = 453.592; 8 | const GRAMS_PER_OUNCE = 28.3495; 9 | 10 | const milesToKm: conversion = m => m * KILOMETERS_PER_MILE; 11 | const kmToMiles: conversion = k => k / KILOMETERS_PER_MILE; 12 | 13 | const poundsToKg: conversion = p => p * (GRAMS_PER_POUND / 1000); 14 | const kgToPounds: conversion = k => k / (GRAMS_PER_POUND / 1000); 15 | 16 | const ouncesToGrams: conversion = o => o * GRAMS_PER_OUNCE; 17 | const gramsToOunces: conversion = g => g / GRAMS_PER_OUNCE; 18 | 19 | /* 20 | It's usually preferred to include all "export" 21 | statements together, at the end of the file. 22 | You need not have a SINGLE export, however. 23 | */ 24 | export { milesToKm, kmToMiles }; 25 | export { poundsToKg, kgToPounds, gramsToOunces, ouncesToGrams }; 26 | export { SPEED_OF_LIGHT_IN_VACUUM_IN_MPS }; 27 | -------------------------------------------------------------------------------- /Chapter02/src/module_counter.1.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | let name: string = ""; 4 | let count: number = 0; 5 | 6 | let get = () => count; 7 | let inc = () => ++count; 8 | let toString = () => `${name}: ${get()}`; 9 | 10 | /* 11 | Since we cannot initialize anything otherwise, 12 | a common pattern is to provide a "init()" function 13 | to do all necessary initializations. 14 | */ 15 | const init = (n: string) => { 16 | name = n; 17 | }; 18 | 19 | export default { inc, toString, init }; // everything else is private 20 | -------------------------------------------------------------------------------- /Chapter02/src/module_counter.2.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | let name: string = ""; 4 | let count: number = 0; 5 | 6 | let get = () => count; 7 | 8 | let throwNotInit = () => { 9 | throw new Error("Not initialized"); 10 | }; 11 | 12 | let inc = throwNotInit; 13 | let toString = throwNotInit; 14 | 15 | /* 16 | Since we cannot initialize anything otherwise, 17 | a common pattern is to provide a "init()" function 18 | to do all necessary initializations. In this case, 19 | "inc()" and "toString()" won't work as expected 20 | if the module wasn't initialized. 21 | */ 22 | const init = (n: string) => { 23 | name = n; 24 | inc = () => ++count; 25 | toString = () => `${name}: ${get()}`; 26 | }; 27 | 28 | export default { inc, toString, init }; // everything else is private 29 | -------------------------------------------------------------------------------- /Chapter02/src/module_counter_usage.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import myCounter from "./module_counter.1.js"; 4 | 5 | /* 6 | Initialize the counter appropriately 7 | */ 8 | myCounter.init("Clicks"); 9 | 10 | /* 11 | The rest would work as before 12 | */ 13 | myCounter.inc(); // 1 14 | myCounter.inc(); // 2 15 | myCounter.inc(); // 3 16 | myCounter.toString(); // "Clicks: 3" 17 | -------------------------------------------------------------------------------- /Chapter02/src/opaque_types.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable no-unused-vars */ 3 | 4 | opaque type dniType: string = string; 5 | type nameType = string; // not opaque! 6 | 7 | const stringToDni = (st: string): dniType => { 8 | /* 9 | do validations on st 10 | if OK, return a dniType 11 | if wrong, throw an error 12 | */ 13 | return (st: dniType); 14 | }; 15 | 16 | export type { dniType, nameType }; 17 | export { stringToDni }; 18 | 19 | class Client { 20 | id: number; 21 | dni: dniType; 22 | name: nameType; 23 | securityToken: string; 24 | 25 | constructor(anId: number, aDni: dniType, aName: nameType) { 26 | this.id = anId; 27 | this.dni = aDni; 28 | this.name = aName; 29 | 30 | this.securityToken = "generate.some.token.somehow"; 31 | } 32 | 33 | showNameAndDni(t: string): void { 34 | console.log(`${t} - Name: ${this.name} DNI: ${this.dni}`); 35 | } 36 | 37 | useTokenForSomething(): void { 38 | // do something with the token 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter02/src/opaque_usage.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable no-unused-vars */ 3 | 4 | import type { dniType, nameType } from "./opaque_types"; 5 | import { stringToDni } from "./opaque_types"; 6 | 7 | function updateClient(id: number, dni: dniType, name: nameType) { 8 | /* 9 | Talk to some server 10 | Update the dni and name for the client with given id 11 | */ 12 | } 13 | 14 | const newDni = "1234567-8"; // supposedly a DNI 15 | const newName = "Kari Nordmann"; 16 | 17 | updateClient(229, newName, newDni); // doesn't work; 2nd argument should be a dni 18 | updateClient(229, newDni, newName); // doesn't work either; same reason 19 | updateClient(229, stringToDni(newDni), newName); // OK! 20 | 21 | /* 22 | Constraints 23 | */ 24 | function showText(st: string) { 25 | console.log(`Important message: ${st}`); 26 | } 27 | const anotherDni: dniType = stringToDni("9876543-2"); 28 | showText(anotherDni); // error, if no subtype constraint is added! 29 | -------------------------------------------------------------------------------- /Chapter02/src/spread_and_rest.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable prefer-const,no-unused-vars,no-unused-labels */ 3 | 4 | let values = [22, 9, 60, 12, 4, 56]; 5 | const maxOfValues = Math.max(...values); // 60 6 | const minOfValues = Math.min(...values); // 4 7 | 8 | let arr1 = [1, 1, 2, 3]; 9 | let arr2 = [13, 21, 34]; 10 | let copyOfArr1 = [...arr1]; 11 | let fibArray = [0, ...arr1, 5, 8, ...arr2]; // the first 10 Fibonacci numbers 12 | 13 | let person = { name: "Juan", age: 24 }; 14 | let copyOfPerson = { ...person }; 15 | let expandedPerson = { ...person, sister: "María" }; 16 | 17 | function average(...nums: Array): number { 18 | let sum = 0; 19 | for (let i = 0; i < nums.length; i++) { 20 | sum += nums[i]; 21 | } 22 | return sum / nums.length; 23 | } 24 | console.log(average(22, 9, 60, 12, 4, 56)); // 27.166667 25 | 26 | const simpleAction = (t: string, d: mixed): Object => { 27 | type: t; 28 | data: d; 29 | return {}; 30 | }; 31 | -------------------------------------------------------------------------------- /Chapter02/src/test_for_chrome/module_counter.SIMPLE.js: -------------------------------------------------------------------------------- 1 | // No Flow anywhere! 2 | 3 | let name = ""; 4 | let count = 0; 5 | 6 | let get = () => count; 7 | 8 | let inc = () => ++count; 9 | let toString = () => `${name}: ${get()}`; 10 | 11 | const init = n => { 12 | name = n; 13 | }; 14 | 15 | export default { inc, toString, init }; // everything else is private 16 | -------------------------------------------------------------------------------- /Chapter02/src/test_for_chrome/module_counter_usage.SIMPLE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | See the console for output. 7 | 8 | 9 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter02/src/types_basic.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable no-unused-vars */ 3 | 4 | let someFlag: boolean; 5 | let greatTotal: number; 6 | let firstName: string; 7 | 8 | function toString(x: number): string { 9 | return String(x); 10 | } 11 | 12 | function addTwo(x: number | string, y: number | string) { 13 | return x + y; 14 | } 15 | 16 | function showValue(z: mixed): void { 17 | // not returning anything 18 | console.log("Showing... ", z); 19 | } 20 | 21 | let numbersList: Array; 22 | numbersList = [22, 9, 60]; // OK 23 | numbersList[1] = "SEP"; // error; cannot assign a string to a number 24 | 25 | let anotherList: number[]; 26 | 27 | let sealedObject: { name: string, age?: number } = { name: "" }; 28 | sealedObject.name = "Ivan Horvat"; // OK 29 | sealedObject.id = 229; // error: key isn't defined in the data type 30 | sealedObject = { age: 57 }; // error: mandatory "name" field is missing 31 | 32 | let unsealedObject = {}; 33 | unsealedObject.id = 229; // OK 34 | 35 | const toString2 = (x: number): string => { 36 | return x + "x"; 37 | }; 38 | 39 | type numberToString = number => string; 40 | const toString3: numberToString = (x: number) => String(x); 41 | -------------------------------------------------------------------------------- /Chapter03/src/doroundmath.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const RM = require("./roundmath.js"); 5 | const { multR, divR } = require("./roundmath.js"); 6 | 7 | console.log(RM.addR(12.348, 4.221)); // 16.57 8 | console.log(multR(22.9, 12.4)); // 283.96 9 | console.log(divR(22, 7)); // 3.14 10 | 11 | // console.log(RM.changeSign(0.07)); // error; RM.changeSign is not a function 12 | -------------------------------------------------------------------------------- /Chapter03/src/flowcomments.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable no-unused-vars */ 3 | 4 | "use strict"; 5 | 6 | let someFlag /*: boolean */; 7 | let greatTotal /*: number */; 8 | let firstName /*: string */; 9 | 10 | function toString(x /*: number */) /*: string */ { 11 | return String(x); 12 | } 13 | 14 | let traffic /*: "red" | "amber" | "green" */; 15 | 16 | /*:: 17 | type pair = [T, T]; 18 | 19 | type pairOfNumbers = pair; 20 | type pairOfStrings = pair; 21 | 22 | type simpleFlag = number | boolean; 23 | type complexObject = { 24 | id: string, 25 | name: string, 26 | indicator: simpleFlag, 27 | listOfValues: Array 28 | }; 29 | */ 30 | 31 | class Person { 32 | /*:: 33 | first: string; 34 | last: string; 35 | */ 36 | 37 | constructor(first /*: string */, last /*: string */) { 38 | this.first = first; 39 | this.last = last; 40 | } 41 | 42 | // ...several methods, snipped out 43 | } 44 | 45 | /*:: 46 | import type { dniType, nameType } from "./opaque_types"; 47 | */ 48 | 49 | /*:: 50 | export type { pairOfNumbers, pairOfStrings }; 51 | */ -------------------------------------------------------------------------------- /Chapter03/src/miniserver.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const http = require("http"); 5 | 6 | http 7 | .createServer((req, res) => { 8 | res.writeHead(200, { "Content-Type": "text/plain" }); 9 | res.end("Server alive!"); 10 | }) 11 | .listen(8080, "localhost"); 12 | 13 | console.log("Mini server ready at http://localhost:8080/"); 14 | -------------------------------------------------------------------------------- /Chapter03/src/process_fork.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | const path = require("path"); 4 | const { fork } = require("child_process"); 5 | 6 | const child = fork(path.resolve("out/process_fork_dir.js")); 7 | 8 | child.send({ path: "/home/fkereki" }); 9 | 10 | child.on("message", data => { 11 | console.log(String(data)); 12 | }); 13 | -------------------------------------------------------------------------------- /Chapter03/src/process_fork_dir.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const fs = require("fs"); 5 | 6 | process.on("message", obj => { 7 | // Received a path to process 8 | fs 9 | .readdirSync(obj.path) 10 | .sort((a, b) => a.localeCompare(b, [], { sensitivity: "base" })) 11 | .filter(file => !file.startsWith(".")) 12 | .forEach(file => process.send && process.send(file)); 13 | }); 14 | -------------------------------------------------------------------------------- /Chapter03/src/process_spawn.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const path = require("path"); 5 | const { spawn } = require("child_process"); 6 | const child = spawn("node", [path.resolve("out/process_spawn_dir.js")]); 7 | 8 | child.stdin.write("/home/fkereki"); 9 | 10 | child.stdout.on("data", data => { 11 | console.log(String(data)); 12 | }); 13 | 14 | child.stdout.on("end", () => { 15 | child.kill(); 16 | }); 17 | -------------------------------------------------------------------------------- /Chapter03/src/process_spawn_dir.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const fs = require("fs"); 5 | 6 | process.stdin.resume(); 7 | 8 | process.stdin.on("data", path => { 9 | // Received a path to process 10 | fs 11 | .readdirSync(path) 12 | .sort((a, b) => a.localeCompare(b, [], { sensitivity: "base" })) 13 | .filter(file => !file.startsWith(".")) 14 | .forEach(file => process.stdout.write(file + "\n")); 15 | 16 | process.stdout.end(); 17 | }); 18 | -------------------------------------------------------------------------------- /Chapter03/src/promisify.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const fs = require("fs"); 5 | const util = require("util"); 6 | 7 | const FILE_TO_READ = "/home/fkereki/MODERNJS/chapter03/src/promisify.js"; // its own source! 8 | 9 | // 1. Original error-first style callback 10 | 11 | function showFileLength1(fileName: string): void { 12 | fs.readFile(fileName, "utf8", (err, text) => { 13 | if (err) { 14 | throw err; 15 | } else { 16 | console.log(`1. Reading, old style: ${text.length} bytes`); 17 | } 18 | }); 19 | } 20 | showFileLength1(FILE_TO_READ); 21 | 22 | // 2. Alternative style using promises 23 | 24 | function showFileLength2(fileName: string): void { 25 | fs.readFile = util.promisify(fs.readFile); 26 | 27 | fs 28 | .readFile(fileName, "utf8") 29 | .then((text: string) => { 30 | console.log(`2. Reading with promises: ${text.length} bytes`); 31 | }) 32 | .catch((err: mixed) => { 33 | throw err; 34 | }); 35 | } 36 | showFileLength2(FILE_TO_READ); 37 | 38 | // 3. Using async/await, and with an arrow function just for variety 39 | 40 | const showFileLength3 = async (fileName: string) => { 41 | fs.readFile = util.promisify(fs.readFile); 42 | 43 | try { 44 | const text: string = await fs.readFile(fileName, "utf8"); 45 | console.log(`3. Reading with async/await: ${text.length} bytes`); 46 | } catch (err) { 47 | throw err; 48 | } 49 | }; 50 | showFileLength3(FILE_TO_READ); 51 | -------------------------------------------------------------------------------- /Chapter03/src/roundmath.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | // These won't be exported: 5 | 6 | const roundToCents = (x: number): number => Math.round(x * 100) / 100; 7 | 8 | const changeSign = (x: number): number => -x; 9 | 10 | // The following will be exported: 11 | 12 | const addR = (x: number, y: number): number => roundToCents(x + y); 13 | 14 | const subR = (x: number, y: number): number => addR(x, changeSign(y)); 15 | 16 | const multR = (x: number, y: number): number => roundToCents(x * y); 17 | 18 | const divR = (x: number, y: number): number => { 19 | if (y === 0) { 20 | throw new Error("Divisor must be nonzero"); 21 | } else { 22 | return roundToCents(x / y); 23 | } 24 | }; 25 | 26 | /* 27 | NOTES: 28 | 1. Exports are all together, at the end, per convention 29 | 2. roundToCents and changeSign are not exported, on purpose 30 | */ 31 | exports.addR = addR; 32 | exports.subR = subR; 33 | exports.multR = multR; 34 | exports.divR = divR; 35 | -------------------------------------------------------------------------------- /Chapter03/src/zip_files.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const zlib = require("zlib"); 5 | const fs = require("fs"); 6 | 7 | const inputStream = fs.createReadStream( 8 | "/home/fkereki/Documents/CHURCHES - Digital Taxonomy.pdf" 9 | ); 10 | 11 | const gzipStream = zlib.createGzip(); 12 | 13 | const outputStream = fs.createWriteStream( 14 | "/home/fkereki/Documents/CHURCHES.gz" 15 | ); 16 | 17 | inputStream.pipe(gzipStream).pipe(outputStream); 18 | -------------------------------------------------------------------------------- /Chapter03/src/zip_send.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const http = require("http"); 5 | const zlib = require("zlib"); 6 | const fs = require("fs"); 7 | 8 | http 9 | .createServer(function(request, response) { 10 | // Tell the client, this is a zip file. 11 | response.writeHead(200, { 12 | "Content-Type": "application/zip", 13 | "Content-disposition": "attachment; filename=churches.gz" 14 | }); 15 | 16 | const inputStream = fs.createReadStream( 17 | "/home/fkereki/Documents/CHURCHES - Digital Taxonomy.pdf" 18 | ); 19 | 20 | const gzipStream = zlib.createGzip(); 21 | 22 | inputStream.pipe(gzipStream).pipe(response); 23 | }) 24 | .listen(8080, "localhost"); 25 | -------------------------------------------------------------------------------- /Chapter04/flags/america/north/CA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/america/north/CA.png -------------------------------------------------------------------------------- /Chapter04/flags/america/north/MX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/america/north/MX.png -------------------------------------------------------------------------------- /Chapter04/flags/america/north/US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/america/north/US.png -------------------------------------------------------------------------------- /Chapter04/flags/america/south/AR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/america/south/AR.png -------------------------------------------------------------------------------- /Chapter04/flags/america/south/BR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/america/south/BR.png -------------------------------------------------------------------------------- /Chapter04/flags/america/south/PY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/america/south/PY.png -------------------------------------------------------------------------------- /Chapter04/flags/america/south/UY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/america/south/UY.png -------------------------------------------------------------------------------- /Chapter04/flags/europe/DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/europe/DE.png -------------------------------------------------------------------------------- /Chapter04/flags/europe/ES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/europe/ES.png -------------------------------------------------------------------------------- /Chapter04/flags/europe/FR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/europe/FR.png -------------------------------------------------------------------------------- /Chapter04/flags/europe/GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/europe/GB.png -------------------------------------------------------------------------------- /Chapter04/flags/europe/IT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/europe/IT.png -------------------------------------------------------------------------------- /Chapter04/flags/europe/PT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter04/flags/europe/PT.png -------------------------------------------------------------------------------- /Chapter04/flags/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Go Squared Ltd. http://www.gosquared.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Chapter04/src/cors_request.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter04/src/cors_server.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const cors = require("cors"); 6 | 7 | const app = express(); 8 | 9 | app.use(cors()); 10 | 11 | app.get("/", (req, res) => res.send("Server alive, with CORS!")); 12 | 13 | app.listen(8080, () => 14 | console.log("CORS server ready at http://localhost:8080/!") 15 | ); 16 | -------------------------------------------------------------------------------- /Chapter04/src/get_parameters.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | 7 | const bodyParser = require("body-parser"); 8 | app.use(bodyParser.urlencoded({extended:false})); 9 | 10 | app.use("*", (req, res) => { 11 | console.log(req.query, req.body); 12 | res.send("Server alive, with Express!"); 13 | }); 14 | 15 | // eslint-disable-next-line no-unused-vars 16 | app.use((err, req, res, next) => { 17 | console.error("Error....", err.message); 18 | res.status(500).send("INTERNAL SERVER ERROR"); 19 | }); 20 | 21 | app.listen(8080, () => 22 | console.log( 23 | "Mini server (with Express) ready at http://localhost:8080/!" 24 | ) 25 | ); 26 | -------------------------------------------------------------------------------- /Chapter04/src/hello_world.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | 7 | app.get("/", (req, res) => res.send("Server alive, with Express!")); 8 | 9 | app.listen(8080, () => 10 | console.log( 11 | "Mini server (with Express) ready at http://localhost:8080/!" 12 | ) 13 | ); 14 | -------------------------------------------------------------------------------- /Chapter04/src/helmet_world.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | 7 | const helmet = require("helmet"); 8 | app.use(helmet()); 9 | 10 | app.get("/", (req, res) => res.send("Server alive, with Express!")); 11 | 12 | app.listen(8081, () => 13 | console.log( 14 | "Mini server (with Express) ready at http://localhost:8080/!" 15 | ) 16 | ); 17 | -------------------------------------------------------------------------------- /Chapter04/src/http_server.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | const http = require("http"); 7 | 8 | http.createServer(app).listen(8080); 9 | 10 | app.use((req, res, next) => { 11 | if (req.secure) { 12 | next(); 13 | } else { 14 | res.redirect( 15 | `https://${req.headers.host.replace(/8080/, "8443")}${req.url}` 16 | ); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /Chapter04/src/https_server.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | const https = require("https"); 7 | const fs = require("fs"); 8 | const path = require("path"); 9 | 10 | const keysPath = path.join(__dirname, "../../certificates"); 11 | const ca = fs.readFileSync(`${keysPath}/modernjsbook.csr`); 12 | const cert = fs.readFileSync(`${keysPath}/modernjsbook.crt`); 13 | const key = fs.readFileSync(`${keysPath}/modernjsbook.key`); 14 | 15 | https.createServer({ ca, cert, key }, app).listen(8443); 16 | 17 | app.get("/", (req, res) => res.send("Secure server!")); 18 | -------------------------------------------------------------------------------- /Chapter04/src/middleware.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | 7 | app.use((req, res, next) => { 8 | console.log("Logger... ", new Date(), req.method, req.path); 9 | next(); 10 | }); 11 | 12 | app.use((req, res, next) => { 13 | if (req.method === "DELETE") { 14 | next(new Error("DELETEs are not accepted!")); 15 | } else { 16 | res.send("Server alive, with Express!"); 17 | } 18 | }); 19 | 20 | // eslint-disable-next-line no-unused-vars 21 | app.use((err, req, res, next) => { 22 | console.error("Error....", err.message); 23 | res.status(500).send("INTERNAL SERVER ERROR"); 24 | }); 25 | 26 | app.listen(8080, () => 27 | console.log( 28 | "Mini server (with Express) ready at http://localhost:8080/!" 29 | ) 30 | ); 31 | -------------------------------------------------------------------------------- /Chapter04/src/restful_db.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const mariaSQL = require("mariasql"); 5 | const { promisify } = require("util"); 6 | 7 | const DB_HOST = "127.0.0.1"; 8 | const DB_USER = "fkereki"; 9 | const DB_PASS = "modernJS!!"; 10 | const DB_SCHEMA = "world"; 11 | 12 | const getDbConnection = (host, user, password, db) => { 13 | const dbConn = new mariaSQL({ host, user, password, db }); 14 | dbConn.query = promisify(dbConn.query); 15 | return dbConn; 16 | }; 17 | 18 | const dbConn = getDbConnection(DB_HOST, DB_USER, DB_PASS, DB_SCHEMA); 19 | 20 | module.exports = dbConn; 21 | -------------------------------------------------------------------------------- /Chapter04/src/router_cities.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | 6 | const routerCities = express.Router(); 7 | 8 | routerCities.get("/MVD", (req, res) => { 9 | res.send(`GET Montevideo... path=${req.originalUrl}`); 10 | }); 11 | 12 | routerCities.get("/:id", (req, res) => { 13 | res.send(`GET City... ${req.params.id}`); 14 | }); 15 | 16 | routerCities.delete("/:id", (req, res) => { 17 | res.send(`DELETE City... ${req.params.id}`); 18 | }); 19 | 20 | routerCities.post("/", (req, res) => { 21 | res.send(`POST City... ${req.params.id}`); 22 | }); 23 | 24 | routerCities.put("/:id", (req, res) => { 25 | res.send(`PUT City... path=${req.originalUrl}`); 26 | }); 27 | module.exports = routerCities; 28 | -------------------------------------------------------------------------------- /Chapter04/src/router_countries.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | 6 | const routerCountries = express.Router(); 7 | 8 | routerCountries.get("/", (req, res) => { 9 | res.send(`All countries... path=${req.originalUrl}`); 10 | }); 11 | 12 | routerCountries.get("/URUGUAY", (req, res) => { 13 | res.send(`GET UY (Uruguay)... path=${req.originalUrl}`); 14 | }); 15 | 16 | routerCountries.get("/:country", (req, res) => { 17 | res.send(`GET Single country... ${req.params.country}`); 18 | }); 19 | 20 | module.exports = routerCountries; 21 | -------------------------------------------------------------------------------- /Chapter04/src/router_home.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | 6 | const routerHome = express.Router(); 7 | 8 | const routerCountries = require("./router_countries.js"); 9 | const routerRegions = require("./router_regions.js"); 10 | const routerCities = require("./router_cities.js"); 11 | 12 | routerHome.use("/countries", routerCountries); 13 | routerHome.use("/regions", routerRegions); 14 | routerHome.use("/cities", routerCities); 15 | 16 | module.exports = routerHome; 17 | -------------------------------------------------------------------------------- /Chapter04/src/router_regions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | 6 | const routerRegions = express.Router(); 7 | 8 | routerRegions.get("/", (req, res) => { 9 | res.send(`Region GET ALL... `); 10 | }); 11 | 12 | routerRegions.get("/:country", (req, res) => { 13 | res.send(`Region GET ALL FOR Country=${req.params.country}`); 14 | }); 15 | 16 | routerRegions.get("/:country/:id", (req, res) => { 17 | res.send(`Region GET ${req.params.country}/${req.params.id}`); 18 | }); 19 | 20 | routerRegions.delete("/:country/:id", (req, res) => { 21 | res.send(`Region DELETE... ${req.params.country}/${req.params.id}`); 22 | }); 23 | 24 | routerRegions.post("/", (req, res) => { 25 | res.send(`Region POST... `); 26 | }); 27 | 28 | routerRegions.put("/:country/:id", (req, res) => { 29 | res.send(`Region PUT... ${req.params.country}/${req.params.id}`); 30 | }); 31 | 32 | module.exports = routerRegions; 33 | -------------------------------------------------------------------------------- /Chapter04/src/routing.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | 7 | const myRouter = require("./router_home.js"); 8 | 9 | app.use("/", myRouter); 10 | 11 | // eslint-disable-next-line no-unused-vars 12 | app.use((err, req, res, next) => { 13 | console.error("Error....", err.message); 14 | res.status(500).send("INTERNAL SERVER ERROR"); 15 | }); 16 | 17 | app.listen(8080, () => 18 | console.log("Routing ready at http://localhost:8080") 19 | ); 20 | -------------------------------------------------------------------------------- /Chapter04/src/serve_helmet.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const path = require("path"); 6 | const app = express(); 7 | 8 | const helmet = require("helmet"); 9 | app.use(helmet()); 10 | 11 | app.get("/", (req, res) => res.send("Server alive, with Express!")); 12 | 13 | app.get( 14 | "/static", 15 | express.static(path.join(__dirname, "../flags"), { 16 | immutable: true, 17 | maxAge: "30 days" 18 | }) 19 | ); 20 | 21 | // eslint-disable-next-line no-unused-vars 22 | app.use((err, req, res, next) => { 23 | console.error("Error....", err.message); 24 | res.status(500).send("INTERNAL SERVER ERROR"); 25 | }); 26 | 27 | app.listen(8080, () => 28 | console.log( 29 | "Mini Express static server ready at http://localhost:8080/!" 30 | ) 31 | ); 32 | -------------------------------------------------------------------------------- /Chapter04/src/serve_statics.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const path = require("path"); 6 | const app = express(); 7 | 8 | app.get("/", (req, res) => res.send("Server alive, with Express!")); 9 | 10 | app.use( 11 | "/static", 12 | express.static(path.join(__dirname, "../flags"), { 13 | immutable: true, 14 | maxAge: "30 days" 15 | }) 16 | ); 17 | 18 | // eslint-disable-next-line no-unused-vars 19 | app.use((err, req, res, next) => { 20 | console.error("Error....", err.message); 21 | res.status(500).send("INTERNAL SERVER ERROR"); 22 | }); 23 | 24 | app.listen(8080, () => 25 | console.log( 26 | "Mini Express static server ready at http://localhost:8080/!" 27 | ) 28 | ); 29 | -------------------------------------------------------------------------------- /Chapter04/src/serve_statics_alt.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | 7 | const path = require("path"); 8 | 9 | const flagsPath = path.join(__dirname, "../flags"); 10 | 11 | app.get("/uruguay", (req, res) => 12 | res.sendFile(`${flagsPath}/america/south/UY.png`) 13 | ); 14 | 15 | app.get("/england", (req, res) => 16 | res.sendFile(`${flagsPath}/europe/GB.png`) 17 | ); 18 | 19 | app.get("/license", (req, res) => 20 | res.sendFile(`${flagsPath}/license.txt`) 21 | ); 22 | 23 | app.use((err, req, res, next) => { 24 | console.error("Error....", err.message); 25 | res.status(500).send("INTERNAL SERVER ERROR"); 26 | }); 27 | 28 | app.listen(8080, () => 29 | console.log( 30 | "Mini Express static server ready at http://localhost:8080/!" 31 | ) 32 | ); 33 | -------------------------------------------------------------------------------- /Chapter04/src/validate_user.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | /* 5 | In real life, validateUser could check a database, 6 | look into an Active Directory, call another service, 7 | etc. -- but for this demo, let's keep it quite 8 | simple and only accept a single hardcoded user. 9 | */ 10 | 11 | const validateUser = ( 12 | userName: string, 13 | password: string, 14 | callback: (?string, ?string) => void 15 | ) => { 16 | if (!userName || !password) { 17 | callback("Missing user/password", null); 18 | } else if (userName === "fkereki" && password === "modernjsbook") { 19 | callback(null, "fkereki"); // OK, send userName back 20 | } else { 21 | callback("Not valid user", null); 22 | } 23 | }; 24 | 25 | module.exports = validateUser; 26 | -------------------------------------------------------------------------------- /Chapter05/coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /Chapter05/coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter05/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /Chapter05/serv_error.json.log: -------------------------------------------------------------------------------- 1 | {"message":"UNKNOWN ROUTE /invented","level":"warn"} 2 | {"message":"GENERAL ERROR res.say_xyzzy is not a function\nTypeError: res.say_xyzzy is not a function\n at app.get (/home/fkereki/MODERNJS/chapter05/out/morgan_in_winston_server.js:67:9)\n at Layer.handle [as handle_request] (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/layer.js:95:5)\n at next (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/route.js:137:13)\n at Route.dispatch (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/route.js:112:3)\n at Layer.handle [as handle_request] (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/layer.js:95:5)\n at /home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/index.js:281:22\n at Function.process_params (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/index.js:335:12)\n at next (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/index.js:275:10)\n at logger (/home/fkereki/MODERNJS/chapter05/node_modules/morgan/index.js:144:5)\n at Layer.handle [as handle_request] (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/layer.js:95:5)","level":"error"} 3 | -------------------------------------------------------------------------------- /Chapter05/serv_error.txt.log: -------------------------------------------------------------------------------- 1 | 2018-05-28T20:04:04.479Z [serv] warn UNKNOWN ROUTE /invented 2 | 2018-05-28T20:04:07.641Z [serv] error GENERAL ERROR res.say_xyzzy is not a function 3 | TypeError: res.say_xyzzy is not a function 4 | at app.get (/home/fkereki/MODERNJS/chapter05/out/morgan_in_winston_server.js:67:9) 5 | at Layer.handle [as handle_request] (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/layer.js:95:5) 6 | at next (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/route.js:137:13) 7 | at Route.dispatch (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/route.js:112:3) 8 | at Layer.handle [as handle_request] (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/layer.js:95:5) 9 | at /home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/index.js:281:22 10 | at Function.process_params (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/index.js:335:12) 11 | at next (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/index.js:275:10) 12 | at logger (/home/fkereki/MODERNJS/chapter05/node_modules/morgan/index.js:144:5) 13 | at Layer.handle [as handle_request] (/home/fkereki/MODERNJS/chapter05/node_modules/express/lib/router/layer.js:95:5) 14 | -------------------------------------------------------------------------------- /Chapter05/src/restful_db.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const mariaSQL = require("mariasql"); 5 | const { promisify } = require("util"); 6 | 7 | const DB_HOST = "127.0.0.1"; 8 | const DB_USER = "fkereki"; 9 | const DB_PASS = "modernJS!!"; 10 | const DB_SCHEMA = "world"; 11 | 12 | const getDbConnection = (host, user, password, db) => { 13 | const dbConn = new mariaSQL({ host, user, password, db }); 14 | dbConn.query = promisify(dbConn.query); 15 | return dbConn; 16 | }; 17 | 18 | const dbConn = getDbConnection(DB_HOST, DB_USER, DB_PASS, DB_SCHEMA); 19 | 20 | module.exports = dbConn; 21 | -------------------------------------------------------------------------------- /Chapter05/src/restful_regions.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const { deleteRegion } = require("./restful_regions"); 5 | 6 | const mockRes = require("node-mocks-http"); 7 | 8 | describe("deleteRegion", () => { 9 | let mDb; 10 | let mRes; 11 | beforeEach(() => { 12 | mDb = { query: jest.fn() }; 13 | mRes = new mockRes.createResponse(); 14 | }); 15 | 16 | it("should not delete a region with cities", async () => { 17 | mDb.query.mockReturnValueOnce(Promise.resolve([1])); 18 | await deleteRegion(mRes, mDb, "FK", "22"); 19 | expect(mRes.statusCode).toBe(405); 20 | }); 21 | 22 | it("should delete a region without cities", async () => { 23 | mDb.query 24 | .mockReturnValueOnce(Promise.resolve([])) 25 | .mockReturnValueOnce( 26 | Promise.resolve({ 27 | info: { affectedRows: 1 } 28 | }) 29 | ); 30 | await deleteRegion(mRes, mDb, "ST", "12"); 31 | expect(mRes.statusCode).toBe(204); 32 | }); 33 | 34 | it("should produce a 404 for non-existing region", async () => { 35 | mDb.query 36 | .mockReturnValueOnce(Promise.resolve([])) 37 | .mockReturnValueOnce( 38 | Promise.resolve({ 39 | info: { affectedRows: 0 } 40 | }) 41 | ); 42 | await deleteRegion(mRes, mDb, "IP", "24"); 43 | expect(mRes.statusCode).toBe(404); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /Chapter05/src/roundmath.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | // These won't be exported: 5 | 6 | const roundToCents = (x: number): number => Math.round(x * 100) / 100; 7 | 8 | const changeSign = (x: number): number => -x; 9 | 10 | // The following will be exported: 11 | 12 | const addR = (x: number, y: number): number => roundToCents(x + y); 13 | 14 | const subR = (x: number, y: number): number => addR(x, changeSign(y)); 15 | 16 | const multR = (x: number, y: number): number => roundToCents(x * y); 17 | 18 | const divR = (x: number, y: number): number => { 19 | if (y === 0) { 20 | throw new Error("Divisor must be nonzero"); 21 | } else { 22 | return roundToCents(x / y); 23 | } 24 | }; 25 | 26 | /* 27 | NOTES: 28 | 1. Exports are all together, at the end, per convention 29 | 2. roundToCents and changeSign are not exported, on purpose 30 | */ 31 | exports.addR = addR; 32 | exports.subR = subR; 33 | exports.multR = multR; 34 | exports.divR = divR; 35 | -------------------------------------------------------------------------------- /Chapter05/src/roundmath.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const rm = require("./roundmath"); 5 | 6 | describe("addR", () => { 7 | it("should add first and round later", () => { 8 | expect(rm.addR(1.505, 2.505)).toBe(4.01); 9 | }); 10 | 11 | it("should handle negatives", () => { 12 | expect(rm.addR(3.15, -2.149)).toBe(1.0); 13 | }); 14 | }); 15 | 16 | describe("divR", () => { 17 | it("should divide first, then round", () => { 18 | expect(rm.divR(22.96, 0.001)).toBe(22960); 19 | }); 20 | 21 | it("should not divide by zero", () => 22 | expect(() => rm.divR(22, 0)).toThrow()); 23 | }); 24 | -------------------------------------------------------------------------------- /Chapter05/src/show_env.js: -------------------------------------------------------------------------------- 1 | // flow() 2 | "use strict"; 3 | 4 | const dotenv = require("dotenv"); 5 | 6 | console.log(process.env); 7 | 8 | const dev = process.env.NODE_ENV || "development"; 9 | const isDev = dev === "development"; 10 | 11 | if (isDev) { 12 | dotenv.load(); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter05/src/validate_user.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | /* 5 | In real life, validateUser could check a database, 6 | look into an Active Directory, call another service, 7 | etc. -- but for this demo, let's keep it quite 8 | simple and only accept a single hardcoded user. 9 | */ 10 | 11 | const validateUser = ( 12 | userName: string, 13 | password: string, 14 | callback: (?string, ?string) => void 15 | ) => { 16 | if (!userName || !password) { 17 | callback("Missing user/password", null); 18 | } else if (userName === "fkereki" && password === "modernjsbook") { 19 | callback(null, "fkereki"); // OK, send userName back 20 | } else { 21 | callback("Not valid user", null); 22 | } 23 | }; 24 | 25 | module.exports = validateUser; 26 | -------------------------------------------------------------------------------- /Chapter05/src/validate_user.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | "use strict"; 3 | 4 | const validateUser = require("./validate_user"); 5 | 6 | describe("validateUser", () => { 7 | let cb; 8 | 9 | beforeEach(() => { 10 | cb = jest.fn(); 11 | }); 12 | 13 | it("should reject a call with empty user", () => { 14 | validateUser("", "somepass", cb); 15 | expect(cb).toHaveBeenCalled(); 16 | expect(cb).toHaveBeenCalledWith("Missing user/password", null); 17 | }); 18 | 19 | it("should reject a wrong password", () => { 20 | validateUser("fkereki", "wrongpassword", cb); 21 | expect(cb).toHaveBeenCalledWith("Not valid user", null); 22 | }); 23 | 24 | it("should accept a correct password", () => { 25 | validateUser("fkereki", "modernjsbook", cb); 26 | expect(cb).toHaveBeenCalledWith(null, "fkereki"); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /Chapter06/config-overrides.js: -------------------------------------------------------------------------------- 1 | const rewireEslint = require("react-app-rewire-eslint"); 2 | function overrideEslintOptions(options) { 3 | // do stuff with the eslint options... 4 | return options; 5 | } 6 | 7 | /* global module */ 8 | module.exports = function override(config, env) { 9 | config = rewireEslint(config, env, overrideEslintOptions); 10 | return config; 11 | }; 12 | -------------------------------------------------------------------------------- /Chapter06/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter06/public/favicon.ico -------------------------------------------------------------------------------- /Chapter06/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter06/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter06/src/App.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { RegionsInformationTable } from "./components/regionsInformationTable"; 5 | 6 | class App extends React.PureComponent<{}> { 7 | render() { 8 | return ; 9 | } 10 | } 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /Chapter06/src/components/countryFilterBar/countryFilterBar.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | import "../general.css"; 6 | 7 | export class CountryFilterBar extends React.PureComponent<{ 8 | list?: Array<{ code: string, name: string }>, 9 | onSelect: string => void 10 | }> { 11 | static propTypes = { 12 | list: PropTypes.arrayOf(PropTypes.object), 13 | onSelect: PropTypes.func.isRequired 14 | }; 15 | 16 | static defaultProps = { 17 | list: [] 18 | }; 19 | 20 | onSelect = (e: { target: HTMLOptionElement }) => 21 | this.props.onSelect(e.target.value); 22 | 23 | render() { 24 | return ( 25 |
26 | Country:  27 | 35 |
36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter06/src/components/countryFilterBar/countryFilterBar.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This is a description file for `CountryFilterBar`, written with Markdown. 4 | 5 | # Description 6 | 7 | It's a _nice_ component! 8 | -------------------------------------------------------------------------------- /Chapter06/src/components/countryFilterBar/countryFilterBar.old.style.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | import "../general.css"; 6 | 7 | export class CountryFilterBar extends React.PureComponent<{ 8 | list: Array<{ code: string, name: string }>, 9 | onSelect: string => void 10 | }> { 11 | constructor(props) { 12 | super(props); 13 | this.onSelect = this.onSelect.bind(this); 14 | } 15 | 16 | onSelect(e: { target: HTMLOptionElement }) { 17 | this.props.onSelect(e.target.value); 18 | } 19 | 20 | render() { 21 | return ( 22 |
23 | Country:  24 | 32 |
33 | ); 34 | } 35 | } 36 | 37 | CountryFilterBar.propTypes = { 38 | list: PropTypes.arrayOf(PropTypes.object).isRequired, 39 | onSelect: PropTypes.func.isRequired 40 | }; 41 | 42 | CountryFilterBar.defaultProps = { 43 | list: [] 44 | }; 45 | -------------------------------------------------------------------------------- /Chapter06/src/components/countryFilterBar/countryFilterBar.story.js: -------------------------------------------------------------------------------- 1 | /* flow() */ 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | 6 | import { CountryFilterBar } from "."; 7 | 8 | const countries = [ 9 | { code: "AR", name: "Argentine" }, 10 | { code: "BR", name: "Brazil" }, 11 | { code: "PY", name: "Paraguay" }, 12 | { code: "UY", name: "Uruguay" } 13 | ]; 14 | 15 | storiesOf("Country Filter Bar", module).add("with some countries", () => ( 16 | null} /> 17 | )); 18 | -------------------------------------------------------------------------------- /Chapter06/src/components/countryFilterBar/countryFilterBarWithAddons.story.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react"; 3 | import { action } from "@storybook/addon-actions"; 4 | import { withNotes } from "@storybook/addon-notes"; 5 | 6 | import { CountryFilterBar } from "./"; 7 | import markDownText from "./countryFilterBar.md"; 8 | 9 | const countries = [ 10 | { code: "AR", name: "Argentine" }, 11 | { code: "BR", name: "Brazil" }, 12 | { code: "PY", name: "Paraguay" }, 13 | { code: "UY", name: "Uruguay" } 14 | ]; 15 | 16 | storiesOf("Country Filter Bar (with addons)", module).add( 17 | "with some countries - with actions and notes", 18 | withNotes(markDownText)(() => ( 19 | 23 | )) 24 | ); 25 | -------------------------------------------------------------------------------- /Chapter06/src/components/countryFilterBar/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { CountryFilterBar } from "./countryFilterBar.js"; 4 | export { CountryFilterBar }; 5 | -------------------------------------------------------------------------------- /Chapter06/src/components/expandableCard.1/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | import "../general.css"; 7 | 8 | export class ExpandableCard extends React.PureComponent<{ 9 | name: string, 10 | cities: number, 11 | population: number 12 | }> { 13 | static propTypes = { 14 | name: PropTypes.string.isRequired, 15 | cities: PropTypes.number.isRequired, 16 | population: PropTypes.number.isRequired 17 | }; 18 | 19 | render() { 20 | return ( 21 |
22 | NAME:{this.props.name} 23 |
24 | CITIES:{this.props.cities} 25 |
26 | POPULATION:{this.props.population} 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Chapter06/src/components/expandableCard.2/expandableCard.css: -------------------------------------------------------------------------------- 1 | .toggle { 2 | float: right; 3 | } 4 | -------------------------------------------------------------------------------- /Chapter06/src/components/expandableCard.2/expandableCardWithAddon.story.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react"; 3 | 4 | import { withKnobs, text, number } from "@storybook/addon-knobs"; 5 | 6 | import { ExpandableCard } from "./"; 7 | 8 | storiesOf("Expandable Card (with knobs)", module) 9 | .addDecorator(withKnobs) 10 | .add("with normal contents", () => ( 11 | 12 |
CITIES: {number("Number of cities", 12)}
13 |
POPULATION: {number("Population", 54321)}
14 |
15 | )); 16 | -------------------------------------------------------------------------------- /Chapter06/src/components/expandableCard.2/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | import "../general.css"; 7 | import "./expandableCard.css"; 8 | 9 | export class ExpandableCard extends React.PureComponent< 10 | { 11 | children: React.ChildrenArray>, 12 | title: string 13 | }, 14 | { open: boolean } 15 | > { 16 | static propTypes = { 17 | children: PropTypes.arrayOf(PropTypes.element).isRequired, 18 | title: PropTypes.string.isRequired 19 | }; 20 | 21 | state = { 22 | open: false 23 | }; 24 | 25 | toggle = () => { 26 | this.setState(state => ({ open: !state.open })); 27 | }; 28 | 29 | render() { 30 | if (this.state.open) { 31 | return ( 32 |
33 | {this.props.title} 34 |
35 | △ 36 |
37 |
{this.props.children}
38 |
39 | ); 40 | } else { 41 | return ( 42 |
43 | {this.props.title} 44 |
45 | ▽ 46 |
47 |
48 | ); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Chapter06/src/components/general.css: -------------------------------------------------------------------------------- 1 | .bordered { 2 | border: 1px solid blue; 3 | padding: 2px; 4 | margin: 4px; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter06/src/components/resultsDataTable.1/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | import { ExpandableCard } from "../expandableCard.1"; 7 | import "../general.css"; 8 | 9 | export class ResultsDataTable extends React.PureComponent<{ 10 | results: Array<{ 11 | id: string, 12 | name: string, 13 | cities: number, 14 | pop: number 15 | }> 16 | }> { 17 | static propTypes = { 18 | results: PropTypes.arrayOf(PropTypes.object) 19 | }; 20 | 21 | static defaultProps = { 22 | results: [] 23 | }; 24 | 25 | render() { 26 | if (this.props.results.length === 0) { 27 | return
No regions.
; 28 | } else { 29 | return ( 30 |
31 | {this.props.results.map(x => ( 32 | 38 | ))} 39 |
40 | ); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Chapter06/src/components/resultsDataTable.2/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | import { ExpandableCard } from "../expandableCard.2"; 7 | import "../general.css"; 8 | 9 | export class ResultsDataTable extends React.PureComponent<{ 10 | results: Array<{ 11 | id: string, 12 | name: string, 13 | cities: number, 14 | pop: number 15 | }> 16 | }> { 17 | static propTypes = { 18 | results: PropTypes.arrayOf(PropTypes.object).isRequired 19 | }; 20 | 21 | render() { 22 | if (this.props.results.length === 0) { 23 | return
No regions.
; 24 | } else { 25 | return ( 26 |
27 | {this.props.results.map(x => ( 28 | 29 |
CITIES:{x.cities}
30 |
POPULATION:{x.pop}
31 |
32 | ))} 33 |
34 | ); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter06/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter06/src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import ReactDOM from "react-dom"; 5 | import "./index.css"; 6 | import App from "./App"; 7 | 8 | //$FlowFixMe 9 | import registerServiceWorker from "./registerServiceWorker"; 10 | 11 | const root = document.getElementById("root"); 12 | if (root) { 13 | ReactDOM.render(, root); 14 | } 15 | registerServiceWorker(); 16 | -------------------------------------------------------------------------------- /Chapter07/config-overrides.js: -------------------------------------------------------------------------------- 1 | const rewireEslint = require("react-app-rewire-eslint"); 2 | const rewireSass = require("react-app-rewire-scss"); 3 | 4 | function overrideEslintOptions(options) { 5 | // do stuff with the eslint options... 6 | return options; 7 | } 8 | 9 | /* global module */ 10 | module.exports = function override(config, env) { 11 | config = rewireEslint(config, env, overrideEslintOptions); 12 | config = rewireSass(config, env); 13 | return config; 14 | }; 15 | -------------------------------------------------------------------------------- /Chapter07/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter07/public/favicon.ico -------------------------------------------------------------------------------- /Chapter07/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter07/src/App.1.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component } from "react"; 4 | 5 | class App extends Component<{}> { 6 | render() { 7 | const cl = "border border-dark p-2 bg-warning "; 8 | 9 | return ( 10 |
11 |
12 |
2/6
13 |
4
14 |
1
15 |
1
16 |
1
17 |
1/5
18 |
2
19 |
7/3
20 |
4
21 |
1/3
22 |
23 |
24 | ); 25 | } 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /Chapter07/src/App.2.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component } from "react"; 4 | 5 | class App extends Component<{}> { 6 | render() { 7 | const cl = "border border-dark p-2 bg-warning "; 8 | const ch = "border border-dark p-2 bg-dark text-white "; 9 | 10 | return ( 11 |
12 |
13 |
2/6
14 |
4
15 |
1
16 |
21 | 1 22 |
23 |
1/5
24 |
3
25 |
26 |
27 | ); 28 | } 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /Chapter07/src/App.3.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component } from "react"; 4 | 5 | class App extends Component<{}> { 6 | render() { 7 | const cl = "border border-dark p-2 bg-warning "; 8 | const ch = "border border-dark p-2 bg-dark text-white "; 9 | 10 | return ( 11 |
12 |
13 |
2/6
14 |
15 | 0/4 16 |
17 |
2
18 |
2
19 |
1/5
20 |
3
21 |
22 |
TOP
23 |
(MIDDLE)
24 |
BOTTOM
25 |
26 |
4
27 |
28 |
29 | ); 30 | } 31 | } 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /Chapter07/src/components/i18nform/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import LanguageDetector from "i18next-browser-languagedetector"; 3 | 4 | import EN_TEXTS from "./translations.en.json"; 5 | import ES_TEXTS from "./translations.es.json"; 6 | 7 | i18n.use(LanguageDetector).init({ 8 | resources: { 9 | en: { translations: EN_TEXTS }, 10 | es: { translations: ES_TEXTS } 11 | }, 12 | fallbackLng: "en", 13 | ns: ["translations"], 14 | defaultNS: "translations", 15 | debug: true, 16 | interpolation: { 17 | escapeValue: false, 18 | format: function(value, format, lang = i18n.language) { 19 | if (format === "AS_DATE") { 20 | try { 21 | const dd = new Date(value); 22 | return new Intl.DateTimeFormat(lang).format( 23 | new Date( 24 | dd.getTime() + dd.getTimezoneOffset() * 60000 25 | ) 26 | ); 27 | } catch (e) { 28 | return "???"; 29 | } 30 | } else { 31 | return value; 32 | } 33 | } 34 | } 35 | }); 36 | 37 | const t = i18n.t.bind(i18n); // to allow using t(...) instead of i18n.t(...) 38 | 39 | export { i18n, t }; 40 | -------------------------------------------------------------------------------- /Chapter07/src/components/i18nform/i18nform.story.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | 6 | import { I18nForm } from "./"; 7 | 8 | storiesOf("i18n form", module).add("standard", () => ); 9 | -------------------------------------------------------------------------------- /Chapter07/src/components/i18nform/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { I18nForm } from "./i18nform.js"; 4 | export { I18nForm }; 5 | -------------------------------------------------------------------------------- /Chapter07/src/components/i18nform/styles.css: -------------------------------------------------------------------------------- 1 | div { 2 | margin-bottom: 12px; } 3 | -------------------------------------------------------------------------------- /Chapter07/src/components/i18nform/styles.scss: -------------------------------------------------------------------------------- 1 | div { 2 | margin-bottom: 12px; 3 | } 4 | -------------------------------------------------------------------------------- /Chapter07/src/components/i18nform/translations.en.json: -------------------------------------------------------------------------------- 1 | { 2 | "details": "Details", 3 | "number": "How many things?", 4 | "color": "Thing Color", 5 | "send it before": "Send the thing before", 6 | "please enter details": "Please, enter details for your thing:", 7 | "summary": "Your only thing will be there before {{date, AS_DATE}}", 8 | "summary_plural": 9 | "Your {{count}} things will be there before {{date, AS_DATE}}", 10 | "colors": { 11 | "none": "None", 12 | "steel": "Steel", 13 | "sand": "Sand" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter07/src/components/i18nform/translations.es.json: -------------------------------------------------------------------------------- 1 | { 2 | "details": "Detalles", 3 | "number": "¿Cuántas cosas?", 4 | "color": "Color de la cosa", 5 | "send it before": "Enviar antes de", 6 | "please enter details": "Por favor, ingrese detalles para su cosa:", 7 | "summary": "Su única cosa llegará antes de la fecha {{date, AS_DATE}}", 8 | "summary_plural": 9 | "Sus {{count}} cosas llegarán antes del {{date, AS_DATE}}", 10 | "colors": { 11 | "none": "Ninguno", 12 | "steel": "Acero", 13 | "sand": "Arena" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter07/src/components/sassButton/_constants.scss: -------------------------------------------------------------------------------- 1 | $normalColor: green; 2 | $normalText: yellow; 3 | 4 | $alertColor: red; 5 | $alertText: white; 6 | -------------------------------------------------------------------------------- /Chapter07/src/components/sassButton/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin darkenBackground($color) { 2 | background-color: $color; 3 | &:hover { 4 | background-color: darken($color, 25%); 5 | transition: all 0.5s ease; 6 | } 7 | } 8 | 9 | @mixin coloredBoldText($color) { 10 | color: $color; 11 | font-weight: bold; 12 | } 13 | -------------------------------------------------------------------------------- /Chapter07/src/components/sassButton/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { SassButton } from "./sassButton.a11y"; 4 | export { SassButton }; 5 | -------------------------------------------------------------------------------- /Chapter07/src/components/sassButton/sassButton.a11y.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | import "./styles.css"; 7 | 8 | export class SassButton extends React.PureComponent<{ 9 | normal: boolean, 10 | buttonText: string, 11 | onSelect: void => void 12 | }> { 13 | static propTypes = { 14 | normal: PropTypes.bool.isRequired, 15 | buttonText: PropTypes.string.isRequired, 16 | onSelect: PropTypes.func.isRequired 17 | }; 18 | 19 | keyDownAsClick = (e: { keyCode: number }) => { 20 | if (e.keyCode === 32 || e.keyCode === 13) { 21 | this.props.onSelect(); 22 | } 23 | }; 24 | 25 | render() { 26 | return ( 27 |
36 | {this.props.buttonText} 37 |
38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter07/src/components/sassButton/sassButton.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | import "./styles.css"; 7 | 8 | export class SassButton extends React.PureComponent<{ 9 | normal: boolean, 10 | buttonText: string, 11 | onSelect: void => void 12 | }> { 13 | static propTypes = { 14 | normal: PropTypes.bool.isRequired, 15 | buttonText: PropTypes.string.isRequired, 16 | onSelect: PropTypes.func.isRequired 17 | }; 18 | 19 | render() { 20 | return ( 21 |
27 | {this.props.buttonText} 28 |
29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Chapter07/src/components/sassButton/sassButton.story.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | import { action } from "@storybook/addon-actions"; 6 | 7 | import { SassButton } from "./"; 8 | 9 | storiesOf("SASS buttons", module) 10 | .add("normal style", () => ( 11 | 16 | )) 17 | .add("alert style", () => ( 18 | 23 | )); 24 | -------------------------------------------------------------------------------- /Chapter07/src/components/sassButton/styles.css: -------------------------------------------------------------------------------- 1 | .normalButton, .alertButton { 2 | display: inline-block; 3 | text-decoration: none; 4 | padding: 5px 10px; 5 | border-radius: 3px; } 6 | 7 | /* 8 | A simple button for normal situations 9 | */ 10 | .normalButton { 11 | background-color: green; } 12 | .normalButton:hover { 13 | background-color: #000100; 14 | transition: all 0.5s ease; } 15 | .normalButton span { 16 | color: yellow; 17 | font-weight: bold; } 18 | 19 | /* 20 | An alert button for warnings or errors 21 | */ 22 | .alertButton { 23 | background-color: red; } 24 | .alertButton:hover { 25 | background-color: maroon; 26 | transition: all 0.5s ease; } 27 | .alertButton span { 28 | color: white; 29 | font-weight: bold; } 30 | -------------------------------------------------------------------------------- /Chapter07/src/components/sassButton/styles.scss: -------------------------------------------------------------------------------- 1 | @import "_constants.scss"; 2 | @import "_mixins.scss"; 3 | 4 | %baseButton { 5 | display: inline-block; 6 | text-decoration: none; 7 | padding: 5px 10px; 8 | border-radius: 3px; 9 | } 10 | 11 | /* 12 | A simple button for normal situations 13 | */ 14 | .normalButton { 15 | @extend %baseButton; 16 | @include darkenBackground($normalColor); 17 | 18 | span { 19 | @include coloredBoldText($normalText); 20 | } 21 | } 22 | 23 | /* 24 | An alert button for warnings or errors 25 | */ 26 | .alertButton { 27 | @extend %baseButton; 28 | @include darkenBackground($alertColor); 29 | 30 | span { 31 | @include coloredBoldText($alertText); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter07/src/components/styledButton/constants.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export const NORMAL_COLOR = "green"; 4 | export const NORMAL_TEXT = "yellow"; 5 | 6 | export const ALERT_COLOR = "red"; 7 | export const ALERT_TEXT = "white"; 8 | -------------------------------------------------------------------------------- /Chapter07/src/components/styledButton/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { StyledButton } from "./styledButton.a11y"; 4 | export { StyledButton }; 5 | -------------------------------------------------------------------------------- /Chapter07/src/components/styledButton/styledButton.story.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | import { action } from "@storybook/addon-actions"; 6 | 7 | import { StyledButton } from "./"; 8 | 9 | storiesOf("Styled-Components buttons", module) 10 | .add("normal style", () => ( 11 | 16 | )) 17 | .add("alert style", () => ( 18 | 23 | )); 24 | -------------------------------------------------------------------------------- /Chapter07/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./App.4"; 5 | import registerServiceWorker from "./registerServiceWorker"; 6 | import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; 7 | 8 | ReactDOM.render(, document.getElementById("root")); 9 | registerServiceWorker(); 10 | -------------------------------------------------------------------------------- /Chapter07/src/makeResize.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | 5 | const getWindowSize = () => ({ 6 | resized_w_h: window.innerWidth + "x" + window.innerHeight 7 | }); 8 | 9 | export const makeResize = Comp => { 10 | // eslint-disable-next-line react/display-name 11 | return class extends React.Component { 12 | state = getWindowSize(); 13 | 14 | componentDidMount() { 15 | window.addEventListener("resize", this.componentIsResizing); 16 | } 17 | 18 | componentWillUnmount() { 19 | window.removeEventListener("resize", this.componentIsResizing); 20 | } 21 | 22 | componentIsResizing = () => { 23 | this.setState(getWindowSize()); 24 | }; 25 | 26 | render() { 27 | return ; 28 | } 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /Chapter07/src/utilities/index.js: -------------------------------------------------------------------------------- 1 | import { keyToClick } from "./keyToClick"; 2 | 3 | export { keyToClick }; 4 | -------------------------------------------------------------------------------- /Chapter07/src/utilities/keyToClick.js: -------------------------------------------------------------------------------- 1 | export const keyToClick = fn => e => 2 | (e.keyCode === 32 || e.keyCode === 13) && fn(); 3 | -------------------------------------------------------------------------------- /Chapter08/config-overrides.js: -------------------------------------------------------------------------------- 1 | const rewireEslint = require("react-app-rewire-eslint"); 2 | 3 | function overrideEslintOptions(options) { 4 | // do stuff with the eslint options... 5 | return options; 6 | } 7 | 8 | /* global module */ 9 | module.exports = function override(config, env) { 10 | config = rewireEslint(config, env, overrideEslintOptions); 11 | return config; 12 | }; 13 | -------------------------------------------------------------------------------- /Chapter08/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter08", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "babel-core": "^6.26.3", 7 | "babel-eslint": "^8.2.5", 8 | "babel-runtime": "^6.26.0", 9 | "eslint": "^5.0.1", 10 | "eslint-config-recommended": "^3.0.0", 11 | "eslint-plugin-babel": "^5.1.0", 12 | "eslint-plugin-flowtype": "^2.49.3", 13 | "eslint-plugin-react": "^7.10.0", 14 | "flow": "^0.2.3", 15 | "flow-bin": "^0.75.0", 16 | "flow-coverage-report": "^0.5.0", 17 | "flow-typed": "^2.4.0", 18 | "react-app-rewire-eslint": "^0.2.3", 19 | "react-app-rewired": "^1.5.2", 20 | "react-scripts": "1.1.4" 21 | }, 22 | "dependencies": { 23 | "axios": "^0.18.0", 24 | "connected-react-router": "^4.3.0", 25 | "react": "^16.4.1", 26 | "react-dom": "^16.4.1", 27 | "react-loadable": "^5.4.0", 28 | "react-redux": "^5.0.7", 29 | "react-router-dom": "^4.3.1", 30 | "redux": "^4.0.0", 31 | "redux-thunk": "^2.3.0" 32 | }, 33 | "scripts": { 34 | "start": "react-app-rewired start", 35 | "build": "react-app-rewired build", 36 | "test": "react-app-rewired test --env=jsdom", 37 | "eject": "react-app-rewired eject", 38 | "flow": "flow", 39 | "addTypes": "flow-typed install" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Chapter08/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter08/public/favicon.ico -------------------------------------------------------------------------------- /Chapter08/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter08/src/App.counter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component, Fragment } from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { store } from "./counterApp/store"; 7 | import { ConnectedCounter, ConnectedClicksDisplay } from "./counterApp"; 8 | 9 | class App extends Component<{}> { 10 | render() { 11 | return ( 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 | ); 20 | } 21 | } 22 | 23 | export default App; 24 | -------------------------------------------------------------------------------- /Chapter08/src/App.regions.functional.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component, Fragment } from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { 7 | ConnectedCountrySelect, 8 | ConnectedRegionsTable 9 | } from "./regionsApp"; 10 | 11 | import { getCountries, getRegions } from "./regionsApp/serviceApi.js"; 12 | import { store } from "./regionsApp/store.js"; 13 | 14 | const dispatcher = fn => (...args) => store.dispatch(fn(...args)); 15 | 16 | class App extends Component<{}> { 17 | render() { 18 | return ( 19 | 20 | 21 | 25 | 26 | 27 | 28 | ); 29 | } 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /Chapter08/src/App.regions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component, Fragment } from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { 7 | ConnectedCountrySelect, 8 | ConnectedRegionsTable 9 | } from "./regionsApp"; 10 | 11 | import { store } from "./regionsApp/store"; 12 | 13 | class App extends Component<{}> { 14 | render() { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /Chapter08/src/counterApp/clicksDisplay.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { PropTypes } from "prop-types"; 5 | 6 | export class ClicksDisplay extends React.PureComponent<{ 7 | clicks: number 8 | }> { 9 | static propTypes = { 10 | clicks: PropTypes.number.isRequired 11 | }; 12 | 13 | render() { 14 | return
Clicks so far: {this.props.clicks}
; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter08/src/counterApp/clicksDisplay.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { ClicksDisplay } from "./clicksDisplay.component"; 6 | 7 | const getProps = state => ({ 8 | clicks: state.clicks 9 | }); 10 | 11 | export const ConnectedClicksDisplay = connect(getProps)(ClicksDisplay); 12 | -------------------------------------------------------------------------------- /Chapter08/src/counterApp/counter.actions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export const COUNTER_INCREMENT = "counter:increment"; 4 | 5 | export const COUNTER_RESET = "counter:reset"; 6 | 7 | export type CounterAction = { 8 | type: string, 9 | value?: number 10 | }; 11 | 12 | export const reset = () => 13 | ({ 14 | type: COUNTER_RESET 15 | }: CounterAction); 16 | 17 | export const increment = (inc: number) => 18 | ({ 19 | type: COUNTER_INCREMENT, 20 | value: inc 21 | }: CounterAction); 22 | 23 | export const decrement = (dec: number) => 24 | ({ 25 | type: COUNTER_INCREMENT, 26 | value: -dec 27 | }: CounterAction); 28 | 29 | // returning increment(-dec) would have worked as well 30 | -------------------------------------------------------------------------------- /Chapter08/src/counterApp/counter.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { PropTypes } from "prop-types"; 5 | 6 | import { 7 | increment, 8 | decrement, 9 | reset, 10 | CounterAction 11 | } from "./counter.actions"; 12 | 13 | export class Counter extends React.PureComponent<{ 14 | count: number, 15 | dispatch: CounterAction => any 16 | }> { 17 | static propTypes = { 18 | count: PropTypes.number.isRequired, 19 | dispatch: PropTypes.func.isRequired 20 | }; 21 | 22 | onAdd1 = () => this.props.dispatch(increment(1)); 23 | onSub2 = () => this.props.dispatch(decrement(2)); 24 | onReset = () => this.props.dispatch(reset()); 25 | 26 | render() { 27 | return ( 28 |
29 | Value: {this.props.count} 30 |
31 | 32 | 33 | 34 |
35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter08/src/counterApp/counter.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { Counter } from "./counter.component"; 6 | 7 | const getProps = state => ({ count: state.count }); 8 | 9 | export const ConnectedCounter = connect(getProps)(Counter); 10 | -------------------------------------------------------------------------------- /Chapter08/src/counterApp/counter.reducer.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { COUNTER_INCREMENT, COUNTER_RESET } from "./counter.actions"; 4 | 5 | import type { CounterAction } from "./counter.actions.js"; 6 | 7 | export const reducer = ( 8 | state = { 9 | // initial state 10 | count: 0, 11 | clicks: 0 12 | }, 13 | action: CounterAction 14 | ) => { 15 | switch (action.type) { 16 | case COUNTER_INCREMENT: 17 | return { 18 | count: state.count + action.value, 19 | clicks: state.clicks + 1 20 | }; 21 | 22 | case COUNTER_RESET: 23 | return { count: 0, clicks: state.clicks + 1 }; 24 | 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /Chapter08/src/counterApp/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { Counter } from "./counter.component.js"; 4 | import { ConnectedCounter } from "./counter.connected.js"; 5 | 6 | import { ClicksDisplay } from "./clicksDisplay.component.js"; 7 | import { ConnectedClicksDisplay } from "./clicksDisplay.connected.js"; 8 | 9 | export { 10 | Counter, 11 | ConnectedCounter, 12 | ClicksDisplay, 13 | ConnectedClicksDisplay 14 | }; 15 | -------------------------------------------------------------------------------- /Chapter08/src/counterApp/store.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createStore } from "redux"; 4 | 5 | import { reducer } from "./counter.reducer.js"; 6 | 7 | export const store = createStore(reducer); 8 | -------------------------------------------------------------------------------- /Chapter08/src/general.css: -------------------------------------------------------------------------------- 1 | .bordered { 2 | border: 1px solid blue; 3 | padding: 2px; 4 | margin: 4px; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter08/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./App.splitting"; 5 | import registerServiceWorker from "./registerServiceWorker"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /Chapter08/src/regionsApp/countrySelect.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { CountrySelect } from "./countrySelect.component"; 6 | import { getCountries, getRegions } from "./world.actions"; 7 | 8 | const getProps = state => ({ 9 | list: state.countries, 10 | loading: state.loadingCountries 11 | }); 12 | 13 | const getDispatch = dispatch => ({ 14 | getCountries: () => dispatch(getCountries()), 15 | onSelect: c => dispatch(getRegions(c)) 16 | }); 17 | 18 | export const ConnectedCountrySelect = connect( 19 | getProps, 20 | getDispatch 21 | )(CountrySelect); 22 | -------------------------------------------------------------------------------- /Chapter08/src/regionsApp/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ConnectedCountrySelect } from "./countrySelect.connected.js"; 4 | import { ConnectedRegionsTable } from "./regionsTable.connected.js"; 5 | 6 | export { ConnectedCountrySelect, ConnectedRegionsTable }; 7 | -------------------------------------------------------------------------------- /Chapter08/src/regionsApp/regionsTable.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | import "../general.css"; 7 | 8 | export class RegionsTable extends React.PureComponent<{ 9 | list: Array<{ 10 | regionCode: string, 11 | regionName: string 12 | }> 13 | }> { 14 | static propTypes = { 15 | list: PropTypes.arrayOf(PropTypes.object).isRequired 16 | }; 17 | 18 | static defaultProps = { 19 | list: [] 20 | }; 21 | 22 | render() { 23 | if (this.props.list.length === 0) { 24 | return
No regions.
; 25 | } else { 26 | const ordered = [...this.props.list].sort( 27 | (a, b) => (a.regionName < b.regionName ? -1 : 1) 28 | ); 29 | 30 | return ( 31 |
32 | {ordered.map(x => ( 33 |
34 | {x.regionName} 35 |
36 | ))} 37 |
38 | ); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Chapter08/src/regionsApp/regionsTable.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { RegionsTable } from "./regionsTable.component"; 6 | 7 | const getProps = state => ({ 8 | list: state.regions, 9 | loading: state.loadingRegions 10 | }); 11 | 12 | export const ConnectedRegionsTable = connect(getProps)(RegionsTable); 13 | -------------------------------------------------------------------------------- /Chapter08/src/regionsApp/serviceApi.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import axios from "axios"; 4 | 5 | export const getCountriesAPI = () => 6 | axios.get(`http://fk-server:8080/countries`); 7 | 8 | export const getRegionsAPI = (country: string) => 9 | axios.get(`http://fk-server:8080/regions/${country}`); 10 | -------------------------------------------------------------------------------- /Chapter08/src/regionsApp/store.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createStore, applyMiddleware } from "redux"; 4 | import thunk from "redux-thunk"; 5 | 6 | import { reducer } from "./world.reducer"; 7 | 8 | export const store = createStore(reducer, applyMiddleware(thunk)); 9 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/authRoute.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { Route, Redirect } from "react-router-dom"; 5 | import PropTypes from "prop-types"; 6 | 7 | export class Auth extends React.Component<{ 8 | loginRoute: string, 9 | token: string, 10 | location: object 11 | }> { 12 | static propTypes = { 13 | loginRoute: PropTypes.string.isRequired, 14 | token: PropTypes.string.isRequired, 15 | location: PropTypes.object 16 | }; 17 | 18 | render() { 19 | const myProps = { ...this.props }; 20 | if (!myProps.token) { 21 | delete myProps.component; 22 | myProps.render = () => ( 23 | 29 | ); 30 | } 31 | return ; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/authRoute.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { Auth } from "./authRoute.component"; 6 | export const AuthRoute = connect(state => ({ 7 | token: state.token, 8 | loginRoute: "/login" 9 | }))(Auth); 10 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ConnectedLogin } from "./login.connected.js"; 4 | import { ConnectedProtectedRoutes } from "./protectedRoutes.connected.js"; 5 | import { AuthRoute } from "./authRoute.connected.js"; 6 | 7 | export { ConnectedLogin, ConnectedProtectedRoutes, AuthRoute }; 8 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/login.actions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { loginService } from "./serviceApi"; 4 | 5 | export const LOGIN_REQUEST = "login:request"; 6 | export const LOGIN_SUCCESS = "login:success"; 7 | export const LOGIN_FAILURE = "login:failure"; 8 | 9 | export const loginRequest = () => ({ 10 | type: LOGIN_REQUEST 11 | }); 12 | 13 | export const loginSuccess = (token: string) => ({ 14 | type: LOGIN_SUCCESS, 15 | token 16 | }); 17 | 18 | export const loginFailure = () => ({ 19 | type: LOGIN_FAILURE 20 | }); 21 | 22 | // Complex actions: 23 | 24 | export const attemptLogin = ( 25 | user: string, 26 | password: string 27 | ) => async dispatch => { 28 | try { 29 | dispatch(loginRequest()); 30 | // the next line delays execution for 5 seconds: 31 | // await new Promise(resolve => setTimeout(resolve, 5000)); 32 | const result = await loginService(user, password); 33 | dispatch(loginSuccess(result.data)); 34 | } catch (e) { 35 | dispatch(loginFailure()); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/login.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { Login } from "./login.component"; 6 | import { attemptLogin } from "./login.actions"; 7 | 8 | export const ConnectedLogin = connect( 9 | state => ({ 10 | logging: state.logging, 11 | token: state.token 12 | }), 13 | dispatch => ({ 14 | onLogin: (user: string, password: string) => 15 | dispatch(attemptLogin(user, password)) 16 | }) 17 | )(Login); 18 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/login.reducer.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | LOGIN_REQUEST, 5 | LOGIN_SUCCESS, 6 | LOGIN_FAILURE 7 | } from "./login.actions"; 8 | 9 | export const reducer = ( 10 | state: object = { 11 | // initial state 12 | logging: false, 13 | token: "" 14 | }, 15 | action 16 | ) => { 17 | switch (action.type) { 18 | case LOGIN_REQUEST: 19 | return { 20 | ...state, 21 | logging: true, 22 | token: "" 23 | }; 24 | 25 | case LOGIN_SUCCESS: 26 | return { 27 | ...state, 28 | logging: false, 29 | token: action.token 30 | }; 31 | 32 | case LOGIN_FAILURE: 33 | return { 34 | ...state, 35 | logging: false 36 | }; 37 | 38 | default: 39 | return state; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/protectedRoutes.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { Redirect, Switch } from "react-router-dom"; 5 | import PropTypes from "prop-types"; 6 | 7 | export class ProtectedRoutes extends React.Component<{ 8 | token: string, 9 | children: any, 10 | location: object 11 | }> { 12 | static propTypes = { 13 | token: PropTypes.string.isRequired, 14 | children: PropTypes.any, 15 | location: PropTypes.object.isRequired 16 | }; 17 | 18 | render() { 19 | return this.props.token ? ( 20 | {this.props.children} 21 | ) : ( 22 | 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/protectedRoutes.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { ProtectedRoutes } from "./protectedRoutes.component"; 6 | 7 | export const ConnectedProtectedRoutes = connect(state => ({ 8 | token: state.token 9 | }))(ProtectedRoutes); 10 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/serviceApi.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import axios from "axios"; 4 | 5 | export const loginService = (user, password) => 6 | axios.post( 7 | `http://localhost:8443/gettoken`, 8 | `user=${user}&password=${password}` 9 | ); 10 | -------------------------------------------------------------------------------- /Chapter08/src/routingApp/store.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createStore, applyMiddleware } from "redux"; 4 | import thunk from "redux-thunk"; 5 | 6 | import { reducer } from "./login.reducer"; 7 | 8 | export const store = createStore(reducer, applyMiddleware(thunk)); 9 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/alpha.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | 5 | const Alpha = () =>

Alpha

; 6 | 7 | export default Alpha; 8 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/alpha.loadable.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Loadable from "react-loadable"; 4 | 5 | import { LoadingStatus } from "./loadingStatus.component"; 6 | 7 | export const AsyncAlpha = Loadable({ 8 | loader: () => import("./alpha.component"), 9 | loading: LoadingStatus 10 | }); 11 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/bravo.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | 5 | const Bravo = () =>

Bravo

; 6 | 7 | export default Bravo; 8 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/bravo.loadable.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Loadable from "react-loadable"; 4 | 5 | import { LoadingStatus } from "./loadingStatus.component"; 6 | 7 | export const AsyncBravo = Loadable({ 8 | loader: () => import("./bravo.component"), 9 | loading: LoadingStatus 10 | }); 11 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/charlie.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | 5 | const Charlie = () =>

Charlie

; 6 | 7 | export default Charlie; 8 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/charlie.loadable.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Loadable from "react-loadable"; 4 | 5 | import { LoadingStatus } from "./loadingStatus.component"; 6 | 7 | export const AsyncCharlie = Loadable({ 8 | loader: () => import("./charlie.component"), 9 | loading: LoadingStatus 10 | }); 11 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/help.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | 5 | const Help = () =>

Help! SOS!

; 6 | 7 | export default Help; 8 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/help.loadable.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Loadable from "react-loadable"; 4 | 5 | import { LoadingStatus } from "./loadingStatus.component"; 6 | 7 | export const AsyncHelp = Loadable({ 8 | loader: () => import("./help.component"), 9 | loading: LoadingStatus 10 | }); 11 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { AsyncAlpha } from "./alpha.loadable"; 4 | import { AsyncBravo } from "./bravo.loadable"; 5 | import { AsyncCharlie } from "./charlie.loadable"; 6 | import { AsyncZulu } from "./zulu.loadable"; 7 | import { AsyncHelp } from "./help.loadable"; 8 | 9 | export { AsyncAlpha, AsyncBravo, AsyncCharlie, AsyncZulu, AsyncHelp }; 10 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/loadingStatus.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | export class LoadingStatus extends React.Component<{ 7 | isLoading: boolean, 8 | error: boolean 9 | }> { 10 | static propTypes = { 11 | isLoading: PropTypes.bool, 12 | error: PropTypes.bool 13 | }; 14 | 15 | render() { 16 | if (this.props.isLoading) { 17 | return
Loading...
; 18 | } else if (this.props.error) { 19 | return
ERROR: the component could not be loaded.
; 20 | } else { 21 | return null; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/zulu.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | 5 | const Zulu = () =>

Zulu

; 6 | 7 | export default Zulu; 8 | -------------------------------------------------------------------------------- /Chapter08/src/splittingApp/zulu.loadable.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Loadable from "react-loadable"; 4 | 5 | import { LoadingStatus } from "./loadingStatus.component"; 6 | 7 | export const AsyncZulu = Loadable({ 8 | loader: () => import("./zulu.component"), 9 | loading: LoadingStatus 10 | }); 11 | -------------------------------------------------------------------------------- /Chapter09-10/config-overrides.js: -------------------------------------------------------------------------------- 1 | const rewireEslint = require("react-app-rewire-eslint"); 2 | 3 | function overrideEslintOptions(options) { 4 | // do stuff with the eslint options... 5 | return options; 6 | } 7 | 8 | /* global module */ 9 | module.exports = function override(config, env) { 10 | config = rewireEslint(config, env, overrideEslintOptions); 11 | return config; 12 | }; 13 | -------------------------------------------------------------------------------- /Chapter09-10/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Modern-JavaScript-Web-Development-Cookbook/3c889fca7433a67b0b36ba2b2b898ffa92154b5e/Chapter09-10/public/favicon.ico -------------------------------------------------------------------------------- /Chapter09-10/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter09-10/src/App.counter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component, Fragment } from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { store } from "./counterApp/store"; 7 | import { ConnectedCounter, ConnectedClicksDisplay } from "./counterApp"; 8 | 9 | class App extends Component<{}> { 10 | render() { 11 | return ( 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 | ); 20 | } 21 | } 22 | 23 | export default App; 24 | -------------------------------------------------------------------------------- /Chapter09-10/src/App.regions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component, Fragment } from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { 7 | ConnectedCountrySelect, 8 | ConnectedRegionsTable 9 | } from "./regionsApp"; 10 | 11 | import { store } from "./regionsApp/store"; 12 | 13 | class App extends Component<{}> { 14 | render() { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/__snapshots__/clicksDisplay.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`clicksDisplay renders correctly 1`] = ` 4 |
5 | Clicks so far: 6 | 22 7 |
8 | `; 9 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/__snapshots__/counter.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`clicksDisplay renders correctly 1`] = ` 4 |
5 | Value: 6 | 9 7 |
8 | 13 | 18 | 23 |
24 | `; 25 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/clicksDisplay.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { PropTypes } from "prop-types"; 5 | 6 | export class ClicksDisplay extends React.PureComponent<{ 7 | clicks: number 8 | }> { 9 | static propTypes = { 10 | clicks: PropTypes.number.isRequired 11 | }; 12 | 13 | render() { 14 | return
Clicks so far: {this.props.clicks}
; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/clicksDisplay.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { ClicksDisplay } from "./clicksDisplay.component"; 6 | 7 | const getProps = state => ({ 8 | clicks: state.clicks 9 | }); 10 | 11 | export const ConnectedClicksDisplay = connect(getProps)(ClicksDisplay); 12 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/clicksDisplay.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TestRenderer from "react-test-renderer"; 3 | 4 | import { ClicksDisplay } from "./clicksDisplay.component"; 5 | 6 | describe("clicksDisplay", () => { 7 | it("renders correctly", () => { 8 | const tree = TestRenderer.create( 9 | 10 | ).toJSON(); 11 | expect(tree).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/counter.actions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export const COUNTER_INCREMENT = "counter:increment"; 4 | 5 | export const COUNTER_RESET = "counter:reset"; 6 | 7 | export type CounterAction = { 8 | type: string, 9 | value?: number 10 | }; 11 | 12 | export const reset = () => 13 | ({ 14 | type: COUNTER_RESET 15 | }: CounterAction); 16 | 17 | export const increment = (inc: number) => 18 | ({ 19 | type: COUNTER_INCREMENT, 20 | value: inc 21 | }: CounterAction); 22 | 23 | export const decrement = (dec: number) => 24 | ({ 25 | type: COUNTER_INCREMENT, 26 | value: -dec 27 | }: CounterAction); 28 | 29 | // returning increment(-dec) would have worked as well 30 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/counter.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { PropTypes } from "prop-types"; 5 | 6 | import { 7 | increment, 8 | decrement, 9 | reset, 10 | CounterAction 11 | } from "./counter.actions"; 12 | 13 | export class Counter extends React.PureComponent<{ 14 | count: number, 15 | dispatch: CounterAction => any 16 | }> { 17 | static propTypes = { 18 | count: PropTypes.number.isRequired, 19 | dispatch: PropTypes.func.isRequired 20 | }; 21 | 22 | onAdd1 = () => this.props.dispatch(increment(1)); 23 | onSub2 = () => this.props.dispatch(decrement(2)); 24 | onReset = () => this.props.dispatch(reset()); 25 | 26 | render() { 27 | return ( 28 |
29 | Value: {this.props.count} 30 |
31 | 32 | 33 | 34 |
35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/counter.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { Counter } from "./counter.component"; 6 | 7 | const getProps = state => ({ count: state.count }); 8 | 9 | export const ConnectedCounter = connect(getProps)(Counter); 10 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/counter.reducer.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { COUNTER_INCREMENT, COUNTER_RESET } from "./counter.actions"; 4 | 5 | import type { CounterAction } from "./counter.actions.js"; 6 | 7 | export const reducer = ( 8 | state = { 9 | // initial state 10 | count: 0, 11 | clicks: 0 12 | }, 13 | action: CounterAction 14 | ) => { 15 | switch (action.type) { 16 | case COUNTER_INCREMENT: 17 | return { 18 | count: state.count + action.value, 19 | clicks: state.clicks + 1 20 | }; 21 | 22 | case COUNTER_RESET: 23 | return { count: 0, clicks: state.clicks + 1 }; 24 | 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/counter.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TestRenderer from "react-test-renderer"; 3 | 4 | import { Counter } from "./counter.component"; 5 | 6 | describe("clicksDisplay", () => { 7 | it("renders correctly", () => { 8 | const tree = TestRenderer.create( 9 | null} /> 10 | ).toJSON(); 11 | expect(tree).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { Counter } from "./counter.component.js"; 4 | import { ConnectedCounter } from "./counter.connected.js"; 5 | 6 | import { ClicksDisplay } from "./clicksDisplay.component.js"; 7 | import { ConnectedClicksDisplay } from "./clicksDisplay.connected.js"; 8 | 9 | export { 10 | Counter, 11 | ConnectedCounter, 12 | ClicksDisplay, 13 | ConnectedClicksDisplay 14 | }; 15 | -------------------------------------------------------------------------------- /Chapter09-10/src/counterApp/store.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createStore, applyMiddleware } from "redux"; 4 | import { createLogger } from "redux-logger"; 5 | import { composeWithDevTools } from "redux-devtools-extension"; 6 | 7 | import { reducer } from "./counter.reducer.js"; 8 | 9 | const logger = createLogger({ 10 | diff: true, 11 | duration: true 12 | }); 13 | 14 | // The following uses only redux-logger 15 | /* 16 | export const store = 17 | process.env.NODE_ENV === "development" 18 | ? createStore(reducer, applyMiddleware(logger)) 19 | : createStore(reducer); 20 | */ 21 | 22 | // The following uses redux-devtools-extension in addition to redux-logger 23 | 24 | export const store = createStore( 25 | reducer, 26 | composeWithDevTools(applyMiddleware(logger)) 27 | ); 28 | -------------------------------------------------------------------------------- /Chapter09-10/src/general.css: -------------------------------------------------------------------------------- 1 | .bordered { 2 | border: 1px solid blue; 3 | padding: 2px; 4 | margin: 4px; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter09-10/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./App.routing.auth"; 5 | import registerServiceWorker from "./registerServiceWorker"; 6 | 7 | import { log } from "./logging"; 8 | log.error("myapp:SERVICE:LOGIN", `Attempt`, { user: "FK", pass: "who?" }); 9 | log.error("myapp:FORM:INITIAL", "Doing render"); 10 | log.info( 11 | "myapp:SERVICE:ERROR_STORE", 12 | "Reporting problem", 13 | "Something wrong", 14 | 404 15 | ); 16 | log.warn("myapp:SERVICE:LOGIN"); 17 | log.debug("myapp:SERVICE:INFO", "This won't be logged... low level"); 18 | log.info("myapp:SERVICE:GETDATE", "Success", { 19 | day: 22, 20 | month: 9, 21 | year: 60 22 | }); 23 | log.verbose("myapp:SERVICE:LOGIN", "Successful login"); 24 | 25 | ReactDOM.render(, document.getElementById("root")); 26 | registerServiceWorker(); 27 | -------------------------------------------------------------------------------- /Chapter09-10/src/index.without.logging.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./App.routing.auth"; 5 | import registerServiceWorker from "./registerServiceWorker"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /Chapter09-10/src/logging/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import debug from "debug"; 4 | 5 | const WHAT_TO_LOG = "myapp:SERVICE:*"; // change this to suit your needs 6 | const MIN_LEVEL_TO_LOG = "info"; // error, warn, info, verbose, or debug 7 | 8 | const log = { 9 | error() {}, 10 | warn() {}, 11 | info() {}, 12 | verbose() {}, 13 | debug() {} 14 | }; 15 | 16 | const logMessage = ( 17 | color: string, 18 | topic: string, 19 | message: any = "--", 20 | ...rest: any 21 | ) => { 22 | const logger = debug(topic); 23 | logger.color = color; 24 | logger(message, ...rest); 25 | }; 26 | 27 | if (process.env.NODE_ENV === "development") { 28 | localStorage.setItem("debug", WHAT_TO_LOG); 29 | 30 | /* eslint-disable no-fallthrough */ 31 | switch (MIN_LEVEL_TO_LOG) { 32 | case "debug": 33 | log.debug = (topic: string, ...args: any) => 34 | logMessage("gray", topic, ...args); 35 | 36 | case "verbose": 37 | log.verbose = (topic: string, ...args: any) => 38 | logMessage("green", topic, ...args); 39 | 40 | case "info": 41 | log.info = (topic: string, ...args: any) => 42 | logMessage("blue", topic, ...args); 43 | 44 | case "warn": 45 | log.warn = (topic: string, ...args: any) => 46 | logMessage("brown", topic, ...args); 47 | 48 | case "error": 49 | default: 50 | log.error = (topic: string, ...args: any) => 51 | logMessage("red", topic, ...args); 52 | } 53 | } 54 | 55 | export { log }; 56 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/__snapshots__/countryAndRegions.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`App for Regions and Countries renders correctly 1`] = ` 4 |
5 |
6 | Select: 7 | 13 |
14 |
15 | Display: 16 | 19 |
20 |
21 | `; 22 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/__snapshots__/countrySelect.snapshot.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`CountrySelect renders correctly a countries dropdown 1`] = ` 4 |
7 | Country:  8 | 33 |
34 | `; 35 | 36 | exports[`CountrySelect renders correctly when loading, with no countries 1`] = ` 37 |
40 | Loading countries... 41 |
42 | `; 43 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/__snapshots__/regionsTable.snapshot.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`RegionsTable renders correctly a list 1`] = ` 4 |
7 |
8 | Cerro Largo 9 |
10 |
11 | Maldonado 12 |
13 |
14 | Montevideo 15 |
16 |
17 | `; 18 | 19 | exports[`RegionsTable renders correctly an empty list 1`] = ` 20 |
23 | No regions. 24 |
25 | `; 26 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/countryAndRegions.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ShallowRenderer from "react-test-renderer/shallow"; 3 | 4 | import { CountrySelect } from "./countrySelect.component"; 5 | import { RegionsTable } from "./regionsTable.component"; 6 | 7 | class CountryAndRegions extends React.Component { 8 | render() { 9 | return ( 10 |
11 |
12 | Select: 13 | null} 16 | getCountries={() => null} 17 | list={[]} 18 | /> 19 |
20 |
21 | Display: 22 |
23 |
24 | ); 25 | } 26 | } 27 | 28 | describe("App for Regions and Countries", () => { 29 | it("renders correctly", () => { 30 | const tree = new ShallowRenderer().render(); 31 | expect(tree).toMatchSnapshot(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/countrySelect.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { CountrySelect } from "./countrySelect.component"; 6 | import { getCountries, getRegions } from "./world.actions"; 7 | 8 | const getProps = state => ({ 9 | list: state.countries, 10 | loading: state.loadingCountries 11 | }); 12 | 13 | const getDispatch = dispatch => ({ 14 | getCountries: () => dispatch(getCountries()), 15 | onSelect: c => dispatch(getRegions(c)) 16 | }); 17 | 18 | export const ConnectedCountrySelect = connect( 19 | getProps, 20 | getDispatch 21 | )(CountrySelect); 22 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/countrySelect.snapshot.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import TestRenderer from "react-test-renderer"; 5 | 6 | import { CountrySelect } from "./countrySelect.component"; 7 | 8 | describe("CountrySelect", () => { 9 | it("renders correctly when loading, with no countries", () => { 10 | const tree = TestRenderer.create( 11 | null} 14 | getCountries={() => null} 15 | list={[]} 16 | /> 17 | ).toJSON(); 18 | expect(tree).toMatchSnapshot(); 19 | }); 20 | 21 | it("renders correctly a countries dropdown", () => { 22 | const tree = TestRenderer.create( 23 | null} 26 | getCountries={() => null} 27 | list={[ 28 | { 29 | countryCode: "UY", 30 | countryName: "Uruguay" 31 | }, 32 | { 33 | countryCode: "AR", 34 | countryName: "Argentina" 35 | }, 36 | { 37 | countryCode: "BR", 38 | countryName: "Brazil" 39 | } 40 | ]} 41 | /> 42 | ).toJSON(); 43 | expect(tree).toMatchSnapshot(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ConnectedCountrySelect } from "./countrySelect.connected.js"; 4 | import { ConnectedRegionsTable } from "./regionsTable.connected.js"; 5 | 6 | export { ConnectedCountrySelect, ConnectedRegionsTable }; 7 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/regionsTable.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | 6 | import "../general.css"; 7 | 8 | export class RegionsTable extends React.PureComponent<{ 9 | list: Array<{ 10 | regionCode: string, 11 | regionName: string 12 | }> 13 | }> { 14 | static propTypes = { 15 | list: PropTypes.arrayOf(PropTypes.object) 16 | }; 17 | 18 | static defaultProps = { 19 | list: [] 20 | }; 21 | 22 | render() { 23 | if (this.props.list.length === 0) { 24 | return
No regions.
; 25 | } else { 26 | const ordered = [...this.props.list].sort( 27 | (a, b) => (a.regionName < b.regionName ? -1 : 1) 28 | ); 29 | 30 | return ( 31 |
32 | {ordered.map(x => ( 33 |
34 | {x.regionName} 35 |
36 | ))} 37 |
38 | ); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/regionsTable.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { RegionsTable } from "./regionsTable.component"; 6 | 7 | export const getProps = state => ({ 8 | list: state.regions, 9 | loading: state.loadingRegions 10 | }); 11 | 12 | export const ConnectedRegionsTable = connect(getProps)(RegionsTable); 13 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/regionsTable.connected.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { getProps } from "./regionsTable.connected.js"; 4 | 5 | describe("getProps for RegionsTable", () => { 6 | it("should extract regions and loading", () => { 7 | const initialState = { 8 | loadingCountries: false, 9 | currentCountry: "whatever", 10 | countries: [{ other: 1 }, { other: 2 }, { other: 3 }], 11 | loadingRegions: false, 12 | regions: [{ something: 1 }, { something: 2 }] 13 | }; 14 | const initialJSON = JSON.stringify(initialState); 15 | 16 | expect(getProps(initialState)).toEqual({ 17 | list: [{ something: 1 }, { something: 2 }], 18 | loading: false 19 | }); 20 | expect(JSON.stringify(initialState)).toBe(initialJSON); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/regionsTable.snapshot.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import TestRenderer from "react-test-renderer"; 5 | 6 | import { RegionsTable } from "./regionsTable.component"; 7 | 8 | describe("RegionsTable", () => { 9 | it("renders correctly an empty list", () => { 10 | const tree = TestRenderer.create( 11 | 12 | ).toJSON(); 13 | expect(tree).toMatchSnapshot(); 14 | }); 15 | 16 | it("renders correctly a list", () => { 17 | const tree = TestRenderer.create( 18 | 37 | ).toJSON(); 38 | expect(tree).toMatchSnapshot(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/regionsTable.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import Enzyme from "enzyme"; 5 | import Adapter from "enzyme-adapter-react-16"; 6 | 7 | import { RegionsTable } from "./regionsTable.component"; 8 | 9 | Enzyme.configure({ adapter: new Adapter() }); 10 | 11 | describe("RegionsTable", () => { 12 | it("renders correctly an empty list", () => { 13 | const wrapper = Enzyme.render(); 14 | expect(wrapper.text()).toContain("No regions."); 15 | }); 16 | 17 | it("renders correctly a list", () => { 18 | const wrapper = Enzyme.render( 19 | 38 | ); 39 | expect(wrapper.text()).toContain("Montevideo"); 40 | expect(wrapper.text()).toContain("Maldonado"); 41 | expect(wrapper.text()).toContain("Cerro Largo"); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/serviceApi.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import axios from "axios"; 4 | 5 | export const getCountriesAPI = () => 6 | axios.get(`http://fk-server:8080/countries`); 7 | 8 | export const getRegionsAPI = country => 9 | axios.get(`http://fk-server:8080/regions/${country}`); 10 | -------------------------------------------------------------------------------- /Chapter09-10/src/regionsApp/store.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createStore, applyMiddleware } from "redux"; 4 | import thunk from "redux-thunk"; 5 | import { createLogger } from "redux-logger"; 6 | import { composeWithDevTools } from "redux-devtools-extension"; 7 | 8 | import { reducer } from "./world.reducer.js"; 9 | 10 | const logger = createLogger({ duration: true }); 11 | 12 | // The following uses redux-logger, only. 13 | /* 14 | export const store = createStore(reducer, applyMiddleware(thunk, logger)); 15 | */ 16 | 17 | // The following uses redux-devtools-extension in addition to redux-logger 18 | 19 | export const store = createStore( 20 | reducer, 21 | composeWithDevTools(applyMiddleware(thunk, logger)) 22 | ); 23 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/authRoute.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { Route, Redirect } from "react-router-dom"; 5 | import PropTypes from "prop-types"; 6 | 7 | export class Auth extends React.Component<{ 8 | loginRoute: string, 9 | token: string, 10 | location: object 11 | }> { 12 | static propTypes = { 13 | loginRoute: PropTypes.string.isRequired, 14 | token: PropTypes.string.isRequired, 15 | location: PropTypes.object 16 | }; 17 | 18 | render() { 19 | const myProps = { ...this.props }; 20 | if (!myProps.token) { 21 | delete myProps.component; 22 | myProps.render = () => ( 23 | 29 | ); 30 | } 31 | return ; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/authRoute.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { Auth } from "./authRoute.component"; 6 | export const AuthRoute = connect(state => ({ 7 | token: state.token, 8 | loginRoute: "/login" 9 | }))(Auth); 10 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ConnectedLogin } from "./login.connected.js"; 4 | import { ConnectedProtectedRoutes } from "./protectedRoutes.connected.js"; 5 | import { AuthRoute } from "./authRoute.connected.js"; 6 | 7 | export { ConnectedLogin, ConnectedProtectedRoutes, AuthRoute }; 8 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/login.actions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { loginService } from "./serviceApi"; 4 | 5 | export const LOGIN_REQUEST = "login:request"; 6 | export const LOGIN_SUCCESS = "login:success"; 7 | export const LOGIN_FAILURE = "login:failure"; 8 | 9 | export const loginRequest = () => ({ 10 | type: LOGIN_REQUEST 11 | }); 12 | 13 | export const loginSuccess = (token: string) => ({ 14 | type: LOGIN_SUCCESS, 15 | token 16 | }); 17 | 18 | export const loginFailure = () => ({ 19 | type: LOGIN_FAILURE 20 | }); 21 | 22 | // Complex actions: 23 | 24 | export const attemptLogin = ( 25 | user: string, 26 | password: string 27 | ) => async dispatch => { 28 | try { 29 | dispatch(loginRequest()); 30 | // the next line delays execution for 5 seconds: 31 | // await new Promise(resolve => setTimeout(resolve, 5000)); 32 | const result = await loginService(user, password); 33 | dispatch(loginSuccess(result.data)); 34 | } catch (e) { 35 | dispatch(loginFailure()); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/login.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { Login } from "./login.component"; 6 | import { attemptLogin } from "./login.actions"; 7 | 8 | export const ConnectedLogin = connect( 9 | state => ({ 10 | logging: state.logging, 11 | token: state.token 12 | }), 13 | dispatch => ({ 14 | onLogin: (user: string, password: string) => 15 | dispatch(attemptLogin(user, password)) 16 | }) 17 | )(Login); 18 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/login.reducer.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | LOGIN_REQUEST, 5 | LOGIN_SUCCESS, 6 | LOGIN_FAILURE 7 | } from "./login.actions"; 8 | 9 | export const reducer = ( 10 | state: object = { 11 | // initial state 12 | logging: false, 13 | token: "" 14 | }, 15 | action 16 | ) => { 17 | switch (action.type) { 18 | case LOGIN_REQUEST: 19 | return { 20 | ...state, 21 | logging: true, 22 | token: "" 23 | }; 24 | 25 | case LOGIN_SUCCESS: 26 | return { 27 | ...state, 28 | logging: false, 29 | token: action.token 30 | }; 31 | 32 | case LOGIN_FAILURE: 33 | return { 34 | ...state, 35 | logging: false 36 | }; 37 | 38 | default: 39 | return state; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/protectedRoutes.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { Redirect, Switch } from "react-router-dom"; 5 | import PropTypes from "prop-types"; 6 | 7 | export class ProtectedRoutes extends React.Component<{ 8 | token: string, 9 | children: any, 10 | location: object 11 | }> { 12 | static propTypes = { 13 | token: PropTypes.string.isRequired, 14 | children: PropTypes.any, 15 | location: PropTypes.object.isRequired 16 | }; 17 | 18 | render() { 19 | return this.props.token ? ( 20 | {this.props.children} 21 | ) : ( 22 | 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/protectedRoutes.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { ProtectedRoutes } from "./protectedRoutes.component"; 6 | 7 | export const ConnectedProtectedRoutes = connect(state => ({ 8 | token: state.token 9 | }))(ProtectedRoutes); 10 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/serviceApi.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import axios from "axios"; 4 | 5 | export const loginService = (user, password) => 6 | axios.post( 7 | `http://localhost:8443/gettoken`, 8 | `user=${user}&password=${password}` 9 | ); 10 | -------------------------------------------------------------------------------- /Chapter09-10/src/routingApp/store.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createStore, applyMiddleware } from "redux"; 4 | import thunk from "redux-thunk"; 5 | import { createLogger } from "redux-logger"; 6 | import { composeWithDevTools } from "redux-devtools-extension"; 7 | import { connectRouter, routerMiddleware } from "connected-react-router"; 8 | import { createBrowserHistory } from "history"; 9 | 10 | import { reducer } from "./login.reducer"; 11 | 12 | const logger = createLogger({ duration: true }); 13 | 14 | export const history = createBrowserHistory(); 15 | 16 | export const store = createStore( 17 | connectRouter(history)(reducer), 18 | composeWithDevTools( 19 | applyMiddleware(routerMiddleware(history), thunk, logger) 20 | ) 21 | ); 22 | -------------------------------------------------------------------------------- /Chapter11/App.adaptive.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { store } from "./src/adaptiveApp/store"; 7 | import { Main } from "./src/adaptiveApp/main"; 8 | 9 | export default class App extends React.PureComponent<> { 10 | render() { 11 | return ( 12 | 13 |
14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter11/App.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { store } from "./src/regionsApp/store"; 7 | import { Main } from "./src/regionsApp/main"; 8 | 9 | export default class App extends React.PureComponent<> { 10 | render() { 11 | return ( 12 | 13 |
14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter11/App.original.fixed.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { StyleSheet, Text, View } from "react-native"; 5 | 6 | export default class App extends React.Component<> { 7 | render() { 8 | return ( 9 | 10 | Open up App.js to start working on your app! 11 | Changes you make will automatically reload. 12 | Shake your phone to open the developer menu. 13 | 14 | ); 15 | } 16 | } 17 | 18 | const white: string = "#fff"; 19 | 20 | const styles = StyleSheet.create({ 21 | container: { 22 | flex: 1, 23 | backgroundColor: white, 24 | alignItems: "center", 25 | justifyContent: "center" 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /Chapter11/App.original.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { StyleSheet, Text, View } from "react-native"; 3 | 4 | export default class App extends React.Component { 5 | render() { 6 | return ( 7 | 8 | Open up App.js to start working on your app! 9 | Changes you make will automatically reload. 10 | Shake your phone to open the developer menu. 11 | 12 | ); 13 | } 14 | } 15 | 16 | const styles = StyleSheet.create({ 17 | container: { 18 | flex: 1, 19 | backgroundColor: "#fff", 20 | alignItems: "center", 21 | justifyContent: "center" 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /Chapter11/App.regions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { store } from "./src/regionsApp/store"; 7 | import { Main } from "./src/regionsApp/main"; 8 | 9 | export default class App extends React.PureComponent<> { 10 | render() { 11 | return ( 12 | 13 |
14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter11/App.routing.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { StatusBar } from "react-native"; 5 | 6 | import { MyDrawer } from "./src/routingApp/drawer"; 7 | 8 | class App extends React.Component { 9 | render() { 10 | return ( 11 | 12 | 15 | ); 16 | } 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /Chapter11/App.styled.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import { Provider } from "react-redux"; 5 | 6 | import { store } from "./src/regionsStyledApp/store"; 7 | import { ConnectedMain } from "./src/regionsStyledApp/main.connected"; 8 | 9 | export default class App extends React.PureComponent<> { 10 | render() { 11 | return ( 12 | 13 | 14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter11/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './App'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders without crashing', () => { 7 | const rendered = renderer.create().toJSON(); 8 | expect(rendered).toBeTruthy(); 9 | }); 10 | -------------------------------------------------------------------------------- /Chapter11/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "sdkVersion": "27.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Chapter11/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter11", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "eslint": "^5.5.0", 7 | "eslint-config-recommended": "^3.0.0", 8 | "eslint-plugin-babel": "^5.1.0", 9 | "eslint-plugin-flowtype": "^2.50.0", 10 | "eslint-plugin-react": "^7.11.1", 11 | "eslint-plugin-react-native": "^3.2.1", 12 | "flow": "^0.2.3", 13 | "flow-bin": "^0.79.1", 14 | "flow-coverage-report": "^0.5.0", 15 | "flow-typed": "^2.5.1", 16 | "jest-expo": "~27.0.0", 17 | "react-native-scripts": "1.14.0", 18 | "react-test-renderer": "16.3.1" 19 | }, 20 | "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js", 21 | "scripts": { 22 | "start": "react-native-scripts start", 23 | "eject": "react-native-scripts eject", 24 | "android": "react-native-scripts android", 25 | "ios": "react-native-scripts ios", 26 | "test": "jest", 27 | "flow": "flow", 28 | "addTypes": "flow-typed install" 29 | }, 30 | "jest": { 31 | "preset": "jest-expo" 32 | }, 33 | "dependencies": { 34 | "axios": "^0.18.0", 35 | "expo": "^27.0.1", 36 | "prop-types": "^15.6.2", 37 | "react": "16.3.1", 38 | "react-native": "~0.55.2", 39 | "react-navigation": "^2.13.0", 40 | "redux-thunk": "^2.3.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter11/src/adaptiveApp/actions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { getDeviceData } from "./device"; 4 | 5 | import type { deviceDataType } from "./device"; 6 | 7 | export const DEVICE_DATA = "device:data"; 8 | 9 | export type deviceDataAction = { 10 | type: string, 11 | deviceData: deviceDataType 12 | }; 13 | 14 | export const setDevice = (deviceData?: object) => 15 | ({ 16 | type: DEVICE_DATA, 17 | deviceData: deviceData || getDeviceData() 18 | }: deviceDataAction); 19 | 20 | /* 21 | A real app would have many more actions! 22 | */ 23 | -------------------------------------------------------------------------------- /Chapter11/src/adaptiveApp/adaptiveView.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | import { View, Text, StyleSheet } from "react-native"; 6 | 7 | import type { deviceDataType } from "./device"; 8 | 9 | const textStyle = StyleSheet.create({ 10 | bigText: { 11 | fontWeight: "bold", 12 | fontSize: 24 13 | } 14 | }); 15 | 16 | export class AdaptiveView extends React.PureComponent<{ 17 | deviceData: deviceDataType 18 | }> { 19 | static propTypes = { 20 | deviceData: PropTypes.object.isRequired 21 | }; 22 | 23 | renderHandset() { 24 | return ( 25 | 26 | 27 | I believe I am a HANDSET currently in 28 | {this.props.deviceData.isPortrait 29 | ? " PORTRAIT " 30 | : " LANDSCAPE "} 31 | orientation 32 | 33 | 34 | ); 35 | } 36 | 37 | renderTablet() { 38 | return ( 39 | 40 | 41 | I think I am a 42 | {this.props.deviceData.isPortrait 43 | ? " PORTRAIT " 44 | : " LANDSCAPE "} 45 | TABLET 46 | 47 | 48 | ); 49 | } 50 | 51 | render() { 52 | return this.props.deviceData.isTablet 53 | ? this.renderTablet() 54 | : this.renderHandset(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Chapter11/src/adaptiveApp/adaptiveView.connected.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { connect } from "react-redux"; 4 | 5 | import { AdaptiveView } from "./adaptiveView.component"; 6 | 7 | const getProps = state => ({ 8 | deviceData: state.deviceData 9 | }); 10 | 11 | export const ConnectedAdaptiveView = connect(getProps)(AdaptiveView); 12 | -------------------------------------------------------------------------------- /Chapter11/src/adaptiveApp/device.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { Dimensions } from "react-native"; 4 | 5 | export type deviceDataType = { 6 | isTablet: boolean, 7 | isPortrait: boolean, 8 | height: number, 9 | width: number, 10 | scale: number, 11 | fontScale: number 12 | }; 13 | 14 | export const getDeviceData = (): deviceDataType => { 15 | const { height, width, scale, fontScale } = Dimensions.get("screen"); 16 | 17 | return { 18 | isTablet: Math.max(height, width) / Math.min(height, width) <= 1.6, 19 | isPortrait: height > width, 20 | height, 21 | width, 22 | scale, 23 | fontScale 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter11/src/adaptiveApp/deviceHandler.component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React from "react"; 4 | import PropTypes from "prop-types"; 5 | import { View } from "react-native"; 6 | 7 | class DeviceHandler extends React.PureComponent<{ 8 | setDevice: () => any 9 | }> { 10 | static propTypes = { 11 | setDevice: PropTypes.func.isRequired 12 | }; 13 | 14 | onLayoutHandler = () => this.props.setDevice(); 15 | 16 | render() { 17 | return