Redux-Thunk working

This commit is contained in:
Federico Kereki
2018-07-23 07:59:46 -03:00
parent b10d1a9dd8
commit c1b237eb60
22 changed files with 2913 additions and 2294 deletions
+1143 -1143
View File
File diff suppressed because it is too large Load Diff
+1131 -1117
View File
File diff suppressed because it is too large Load Diff
+28 -9
View File
@@ -5,8 +5,7 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "flow-remove-types src/ -d out/", "build": "flow-remove-types src/ -d out/",
"buildWithMaps": "buildWithMaps": "flow-remove-types src/ -d out/ --pretty --sourcemaps",
"flow-remove-types src/ -d out/ --pretty --sourcemaps",
"start": "npm run build && node out/doroundmath.js", "start": "npm run build && node out/doroundmath.js",
"start-db": "npm run build && node out/dbaccess.js", "start-db": "npm run build && node out/dbaccess.js",
"nodemon": "nodemon --watch src --delay 1 --exec npm start", "nodemon": "nodemon --watch src --delay 1 --exec npm start",
@@ -20,7 +19,11 @@
"author": "Federico Kereki", "author": "Federico Kereki",
"license": "ISC", "license": "ISC",
"babel": { "babel": {
"presets": ["env", "node", "flow"] "presets": [
"env",
"node",
"flow"
]
}, },
"eslintConfig": { "eslintConfig": {
"parserOptions": { "parserOptions": {
@@ -32,8 +35,14 @@
"node": true, "node": true,
"es6": true "es6": true
}, },
"extends": ["eslint:recommended", "plugin:flowtype/recommended"], "extends": [
"plugins": ["babel", "flowtype"], "eslint:recommended",
"plugin:flowtype/recommended"
],
"plugins": [
"babel",
"flowtype"
],
"rules": { "rules": {
"no-console": "off", "no-console": "off",
"no-var": "error", "no-var": "error",
@@ -41,13 +50,23 @@
"flowtype/no-types-missing-file-annotation": 0 "flowtype/no-types-missing-file-annotation": 0
} }
}, },
"eslintIgnore": ["**/out/*.js"], "eslintIgnore": [
"**/out/*.js"
],
"flow-coverage-report": { "flow-coverage-report": {
"concurrentFiles": 1, "concurrentFiles": 1,
"excludeGlob": ["node_modules/**"], "excludeGlob": [
"includeGlob": ["src/**/*.js"], "node_modules/**"
],
"includeGlob": [
"src/**/*.js"
],
"threshold": 90, "threshold": 90,
"type": ["text", "html", "json"] "type": [
"text",
"html",
"json"
]
}, },
"prettier": { "prettier": {
"tabWidth": 4, "tabWidth": 4,
+145
View File
@@ -0,0 +1,145 @@
/* @flow */
"use strict";
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const dbConn = require("./restful_db.js");
app.get("/", (req, res) => res.send("Secure server!"));
const cors = require("cors");
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
/*
Add here the logic for providing a JWT at /gettoken
and the logic for validating a JWT, as shown earlier
*/
const {
getCountry,
deleteCountry,
putCountry
} = require("./restful_countries.js");
app.get("/countries", (req, res) => getCountry(res, dbConn));
app.get("/countries/:country", (req, res) =>
getCountry(res, dbConn, req.params.country)
);
app.delete("/countries/:country", (req, res) =>
deleteCountry(res, dbConn, req.params.country)
);
// We don't allow a POST to /countries
app.put("/countries/:country", (req, res) =>
putCountry(res, dbConn, req.params.country, req.body.name)
);
const {
getRegion,
deleteRegion,
postRegion,
putRegion
} = require("./restful_regions.js");
app.get("/regions", (req, res) => getRegion(res, dbConn));
app.get("/regions/:country", (req, res) =>
getRegion(res, dbConn, req.params.country)
);
app.get("/regions/:country/:region", (req, res) =>
getRegion(res, dbConn, req.params.country, req.params.region)
);
app.delete("/regions/:country/:region", (req, res) =>
deleteRegion(res, dbConn, req.params.country, req.params.region)
);
app.post("/regions/:country", (req, res) =>
postRegion(res, dbConn, req.params.country, req.body.name)
);
app.put("/regions/:country/:region", (req, res) =>
putRegion(
res,
dbConn,
req.params.country,
req.params.region,
req.body.name
)
);
const {
getCity,
deleteCity,
postCity,
putCity
} = require("./restful_cities.js");
// We do not allow calling /cities to get every city in the world
app.get("/cities/:city", (req, res) =>
getCity(res, dbConn, req.params.city)
);
app.delete("/cities/:city", (req, res) =>
deleteCity(res, dbConn, req.params.city)
);
app.post("/cities/:city", (req, res) =>
postCity(
res,
dbConn,
req.body.name,
req.body.latitude,
req.body.longitude,
req.body.population,
req.body.country,
req.body.region
)
);
app.put("/cities/:city", (req, res) =>
putCity(
res,
dbConn,
req.params.city,
req.body.name,
req.body.latitude,
req.body.longitude,
req.body.population,
req.body.country,
req.body.region
)
);
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
console.error("Error....", err.message);
res.status(500).send("INTERNAL SERVER ERROR");
});
/*
Add here the logic for HTTPS
const https = require("https");
const fs = require("fs");
const path = require("path");
const keysPath = path.join(__dirname, "../../certificates");
const ca = fs.readFileSync(`${keysPath}/modernjsbook.csr`);
const cert = fs.readFileSync(`${keysPath}/modernjsbook.crt`);
const key = fs.readFileSync(`${keysPath}/modernjsbook.key`);
https.createServer({ ca, cert, key }, app);
*/
app.listen(8080, () =>
console.log("Routing ready at http://localhost:8080")
);
+16 -6
View File
@@ -698,6 +698,15 @@
"integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==",
"dev": true "dev": true
}, },
"axios": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
"requires": {
"follow-redirects": "^1.3.0",
"is-buffer": "^1.1.5"
}
},
"babel-code-frame": { "babel-code-frame": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -5737,7 +5746,6 @@
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz",
"integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==", "integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==",
"dev": true,
"requires": { "requires": {
"debug": "^3.1.0" "debug": "^3.1.0"
}, },
@@ -5746,7 +5754,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": { "requires": {
"ms": "2.0.0" "ms": "2.0.0"
} }
@@ -7356,8 +7363,7 @@
"is-buffer": { "is-buffer": {
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
"dev": true
}, },
"is-builtin-module": { "is-builtin-module": {
"version": "1.0.0", "version": "1.0.0",
@@ -9166,8 +9172,7 @@
"ms": { "ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
"dev": true
}, },
"multicast-dns": { "multicast-dns": {
"version": "6.2.3", "version": "6.2.3",
@@ -11986,6 +11991,11 @@
} }
} }
}, },
"redux-thunk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
},
"regenerate": { "regenerate": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+3 -1
View File
@@ -20,10 +20,12 @@
"react-scripts": "1.1.4" "react-scripts": "1.1.4"
}, },
"dependencies": { "dependencies": {
"axios": "^0.18.0",
"react": "^16.4.1", "react": "^16.4.1",
"react-dom": "^16.4.1", "react-dom": "^16.4.1",
"react-redux": "^5.0.7", "react-redux": "^5.0.7",
"redux": "^4.0.0" "redux": "^4.0.0",
"redux-thunk": "^2.3.0"
}, },
"scripts": { "scripts": {
"start": "react-app-rewired start", "start": "react-app-rewired start",
+23
View File
@@ -0,0 +1,23 @@
/* @flow */
import React, { Component, Fragment } from "react";
import { Provider } from "react-redux";
import { store } from "./counterApp/store";
import { ConnectedCounter, ConnectedClicksDisplay } from "./counterApp";
class App extends Component<{}> {
render() {
return (
<Provider store={store}>
<Fragment>
<ConnectedCounter />
<hr />
<ConnectedClicksDisplay />
</Fragment>
</Provider>
);
}
}
export default App;
+32
View File
@@ -0,0 +1,32 @@
/* @flow */
import React, { Component, Fragment } from "react";
import { Provider } from "react-redux";
import {
ConnectedCountrySelect,
ConnectedRegionsTable
} from "./regionsApp";
import { getCountries, getRegions } from "./regionsApp/serviceApi.js";
import { store } from "./regionsApp/store.js";
const dispatcher = fn => (...args) => store.dispatch(fn(...args));
class App extends Component<{}> {
render() {
return (
<Provider store={store}>
<Fragment>
<ConnectedCountrySelect
getCountries={dispatcher(getCountries)}
onSelect={dispatcher(getRegions)}
/>
<ConnectedRegionsTable />
</Fragment>
</Provider>
);
}
}
export default App;
@@ -1,7 +1,7 @@
/* @flow */ /* @flow */
import React from "react"; import React from "../../../../../.cache/typescript/2.9/node_modules/@types/react";
import { PropTypes } from "prop-types"; import { PropTypes } from "../../../../../.cache/typescript/2.9/node_modules/@types/prop-types";
export class ClicksDisplay extends React.PureComponent<{ export class ClicksDisplay extends React.PureComponent<{
clicks: number clicks: number
@@ -1,14 +1,14 @@
/* @flow */ /* @flow */
import React from "react"; import React from "../../../../../.cache/typescript/2.9/node_modules/@types/react";
import { PropTypes } from "prop-types"; import { PropTypes } from "../../../../../.cache/typescript/2.9/node_modules/@types/prop-types";
import { import {
increment, increment,
decrement, decrement,
reset, reset,
CounterAction CounterAction
} from "./counter.actions.js"; } from "./counter.actions";
export class Counter extends React.PureComponent<{ export class Counter extends React.PureComponent<{
count: number, count: number,
+5
View File
@@ -0,0 +1,5 @@
.bordered {
border: 1px solid blue;
padding: 2px;
margin: 4px;
}
+2 -13
View File
@@ -1,19 +1,8 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { ConnectedCounter, ConnectedClicksDisplay } from "./counterApp"; import App from "./App.regions.js";
import { store } from "./counterApp/store";
import registerServiceWorker from "./registerServiceWorker"; import registerServiceWorker from "./registerServiceWorker";
ReactDOM.render( ReactDOM.render(<App />, document.getElementById("root"));
<Provider store={store}>
<React.Fragment>
<ConnectedCounter />
<hr />
<ConnectedClicksDisplay />
</React.Fragment>
</Provider>,
document.getElementById("root")
);
registerServiceWorker(); registerServiceWorker();
@@ -0,0 +1,51 @@
/* @flow */
import React from "react";
import PropTypes from "prop-types";
import "../general.css";
export class CountrySelect extends React.PureComponent<{
dispatch: ({}) => any
}> {
static propTypes = {
loading: PropTypes.bool.isRequired,
list: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelect: PropTypes.func.isRequired,
getCountries: PropTypes.func.isRequired
};
componentDidMount() {
this.props.getCountries();
}
onSelect = (e: { target: HTMLOptionElement }) =>
this.props.onSelect(e.target.value);
render() {
if (this.props.loading) {
return <div className="bordered">Loading countries...</div>;
} else {
const sortedCountries = [...this.props.list].sort(
(a, b) => (a.countryName < b.countryName ? -1 : 1)
);
return (
<div className="bordered">
Country:&nbsp;
<select onChange={this.onSelect}>
<option value="">Select a country:</option>
{sortedCountries.map(x => (
<option
key={x.countryCode}
value={x.countryCode}
>
{x.countryName}
</option>
))}
</select>
</div>
);
}
}
}
@@ -0,0 +1,12 @@
/* @flow */
import { connect } from "react-redux";
import { CountrySelect } from "./countrySelect.component";
const getProps = state => ({
list: state.countries,
loading: state.loadingCountries
});
export const ConnectedCountrySelect = connect(getProps)(CountrySelect);
+6
View File
@@ -0,0 +1,6 @@
/* @flow */
import { ConnectedCountrySelect } from "./countrySelect.connected.js";
import { ConnectedRegionsTable } from "./regionsTable.connected.js";
export { ConnectedCountrySelect, ConnectedRegionsTable };
@@ -0,0 +1,41 @@
/* @flow */
import React from "react";
import PropTypes from "prop-types";
import "../general.css";
export class RegionsTable extends React.PureComponent<{
list: Array<{
regionCode: string,
regionName: string
}>
}> {
static propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired
};
static defaultProps = {
list: []
};
render() {
if (this.props.list.length === 0) {
return <div className="bordered">No regions.</div>;
} else {
const ordered = [...this.props.list].sort(
(a, b) => (a.regionName < b.regionName ? -1 : 1)
);
return (
<div className="bordered">
{ordered.map(x => (
<div key={x.countryCode + "-" + x.regionCode}>
{x.regionName}
</div>
))}
</div>
);
}
}
}
@@ -0,0 +1,12 @@
/* @flow */
import { connect } from "react-redux";
import { RegionsTable } from "./regionsTable.component";
const getProps = state => ({
list: state.regions,
loading: state.loadingRegions
});
export const ConnectedRegionsTable = connect(getProps)(RegionsTable);
+38
View File
@@ -0,0 +1,38 @@
/* @flow */
import axios from "axios";
import {
countriesRequest,
countriesSuccess,
countriesFailure,
regionsRequest,
regionsSuccess,
regionsFailure
} from "./world.actions";
export const getCountries = () => async dispatch => {
try {
dispatch(countriesRequest());
const result = await axios.get(`http://fk-server:8080/countries`);
dispatch(countriesSuccess(result.data));
} catch (e) {
dispatch(countriesFailure());
}
};
export const getRegions = (country: string) => async dispatch => {
if (country) {
try {
dispatch(regionsRequest());
const result = await axios.get(
`http://fk-server:8080/regions/${country}`
);
dispatch(regionsSuccess(result.data));
} catch (e) {
dispatch(regionsFailure());
}
} else {
dispatch(regionsFailure());
}
};
+8
View File
@@ -0,0 +1,8 @@
/* @flow */
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { reducer } from "./worlds.reducer.js";
export const store = createStore(reducer, applyMiddleware(thunk));
+32
View File
@@ -0,0 +1,32 @@
/* @flow */
export const COUNTRIES_REQUEST = "countries:request";
export const COUNTRIES_SUCCESS = "countries:success";
export const COUNTRIES_FAILURE = "countries:failure";
export const REGIONS_REQUEST = "regions:request";
export const REGIONS_SUCCESS = "regions:success";
export const REGIONS_FAILURE = "regions:failure";
export type worldAction = {
type: string,
value?: number
};
export const countriesRequest = () => ({ type: COUNTRIES_REQUEST });
export const countriesSuccess = (listOfCountries: []) => ({
type: COUNTRIES_SUCCESS,
listOfCountries
});
export const countriesFailure = () => ({ type: COUNTRIES_FAILURE });
export const regionsRequest = () => ({ type: REGIONS_REQUEST });
export const regionsSuccess = (listOfRegions: []) => ({
type: REGIONS_SUCCESS,
listOfRegions
});
export const regionsFailure = () => ({ type: REGIONS_FAILURE });
@@ -0,0 +1,70 @@
/* @flow */
import {
COUNTRIES_REQUEST,
COUNTRIES_SUCCESS,
COUNTRIES_FAILURE,
REGIONS_REQUEST,
REGIONS_SUCCESS,
REGIONS_FAILURE
} from "./world.actions";
// import type { CounterAction } from "./world.actions.js";
export const reducer = (
state: object = {
// initial state
loadingCountries: false,
countries: [],
loadingRegions: false,
regions: []
},
action: object
) => {
switch (action.type) {
case COUNTRIES_REQUEST:
return {
...state,
loadingCountries: true,
countries: []
};
case COUNTRIES_SUCCESS:
return {
...state,
loadingCountries: false,
countries: action.listOfCountries
};
case COUNTRIES_FAILURE:
return {
...state,
loadingCountries: false,
countries: []
};
case REGIONS_REQUEST:
return {
...state,
loadingRegions: true,
regions: []
};
case REGIONS_SUCCESS:
return {
...state,
loadingRegions: false,
regions: action.listOfRegions
};
case REGIONS_FAILURE:
return {
...state,
loadingRegions: false,
regions: []
};
default:
return state;
}
};
File diff suppressed because one or more lines are too long