diff --git a/hooks/00_BoilerPlate/package.json b/hooks/00_BoilerPlate/package.json index a259e937..12b2f5b2 100644 --- a/hooks/00_BoilerPlate/package.json +++ b/hooks/00_BoilerPlate/package.json @@ -15,21 +15,21 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" } } diff --git a/hooks/00_BoilerPlate/webpack.config.js b/hooks/00_BoilerPlate/webpack.config.js index bf1cad6c..37715722 100644 --- a/hooks/00_BoilerPlate/webpack.config.js +++ b/hooks/00_BoilerPlate/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./main.ts"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/01_HelloReact/package.json b/hooks/01_HelloReact/package.json index bfa0e4eb..264e71d2 100644 --- a/hooks/01_HelloReact/package.json +++ b/hooks/01_HelloReact/package.json @@ -15,27 +15,27 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/01_HelloReact/webpack.config.js b/hooks/01_HelloReact/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/01_HelloReact/webpack.config.js +++ b/hooks/01_HelloReact/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/02_Properties/package.json b/hooks/02_Properties/package.json index bfa0e4eb..264e71d2 100644 --- a/hooks/02_Properties/package.json +++ b/hooks/02_Properties/package.json @@ -15,27 +15,27 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/02_Properties/src/hello.tsx b/hooks/02_Properties/src/hello.tsx index 38df41c1..b9b1d810 100644 --- a/hooks/02_Properties/src/hello.tsx +++ b/hooks/02_Properties/src/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => ( -

Hello user: {props.userName} !

-); \ No newline at end of file +export const HelloComponent: React.FC = (props) => ( +

Hello user: {props.userName} !

+); diff --git a/hooks/02_Properties/webpack.config.js b/hooks/02_Properties/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/02_Properties/webpack.config.js +++ b/hooks/02_Properties/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/03_State/package.json b/hooks/03_State/package.json index bfa0e4eb..264e71d2 100644 --- a/hooks/03_State/package.json +++ b/hooks/03_State/package.json @@ -15,27 +15,27 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/03_State/src/hello.tsx b/hooks/03_State/src/hello.tsx index 33a0ab73..b6b7aca4 100644 --- a/hooks/03_State/src/hello.tsx +++ b/hooks/03_State/src/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => { +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/03_State/src/nameEdit.tsx b/hooks/03_State/src/nameEdit.tsx index 09c4cd1c..02f7e36d 100644 --- a/hooks/03_State/src/nameEdit.tsx +++ b/hooks/03_State/src/nameEdit.tsx @@ -5,7 +5,7 @@ interface Props { onChange: (e: React.ChangeEvent) => void; } -export const NameEditComponent = (props: Props) => ( +export const NameEditComponent: React.FC = (props) => ( <> diff --git a/hooks/03_State/webpack.config.js b/hooks/03_State/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/03_State/webpack.config.js +++ b/hooks/03_State/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/04_Callback/package.json b/hooks/04_Callback/package.json index bfa0e4eb..264e71d2 100644 --- a/hooks/04_Callback/package.json +++ b/hooks/04_Callback/package.json @@ -15,27 +15,27 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/04_Callback/src/hello.tsx b/hooks/04_Callback/src/hello.tsx index 33a0ab73..b6b7aca4 100644 --- a/hooks/04_Callback/src/hello.tsx +++ b/hooks/04_Callback/src/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => { +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/04_Callback/src/nameEdit.tsx b/hooks/04_Callback/src/nameEdit.tsx index 53edae95..8b1a5ca8 100644 --- a/hooks/04_Callback/src/nameEdit.tsx +++ b/hooks/04_Callback/src/nameEdit.tsx @@ -5,7 +5,7 @@ interface Props { onNameUpdated: (newName: string) => any; } -export const NameEditComponent = (props: Props) => { +export const NameEditComponent: React.FC = (props) => { const [editingName, setEditingName] = React.useState(props.initialUserName); const onChange = (e: React.ChangeEvent) => { diff --git a/hooks/04_Callback/webpack.config.js b/hooks/04_Callback/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/04_Callback/webpack.config.js +++ b/hooks/04_Callback/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/05_Refactor/package.json b/hooks/05_Refactor/package.json index bfa0e4eb..264e71d2 100644 --- a/hooks/05_Refactor/package.json +++ b/hooks/05_Refactor/package.json @@ -15,27 +15,27 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/05_Refactor/src/hello.tsx b/hooks/05_Refactor/src/hello.tsx index 33a0ab73..b6b7aca4 100644 --- a/hooks/05_Refactor/src/hello.tsx +++ b/hooks/05_Refactor/src/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => { +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/05_Refactor/src/nameEdit.tsx b/hooks/05_Refactor/src/nameEdit.tsx index 57f53dd0..c2eebf31 100644 --- a/hooks/05_Refactor/src/nameEdit.tsx +++ b/hooks/05_Refactor/src/nameEdit.tsx @@ -7,7 +7,7 @@ interface Props { onEditingNameUpdated: (newEditingName: string) => any; } -export const NameEditComponent = (props: Props) => { +export const NameEditComponent: React.FC = (props) => { const onChange = (e: React.ChangeEvent) => { props.onEditingNameUpdated(e.target.value); }; diff --git a/hooks/05_Refactor/webpack.config.js b/hooks/05_Refactor/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/05_Refactor/webpack.config.js +++ b/hooks/05_Refactor/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/06_Enable/package.json b/hooks/06_Enable/package.json index bfa0e4eb..264e71d2 100644 --- a/hooks/06_Enable/package.json +++ b/hooks/06_Enable/package.json @@ -15,27 +15,27 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/06_Enable/src/hello.tsx b/hooks/06_Enable/src/hello.tsx index 33a0ab73..b6b7aca4 100644 --- a/hooks/06_Enable/src/hello.tsx +++ b/hooks/06_Enable/src/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => { +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/06_Enable/src/nameEdit.tsx b/hooks/06_Enable/src/nameEdit.tsx index 646a4d26..7f9091e3 100644 --- a/hooks/06_Enable/src/nameEdit.tsx +++ b/hooks/06_Enable/src/nameEdit.tsx @@ -5,10 +5,10 @@ interface Props { editingName: string; onNameUpdated: () => any; onEditingNameUpdated: (newEditingName: string) => any; - disabled : boolean; + disabled: boolean; } -export const NameEditComponent = (props: Props) => { +export const NameEditComponent: React.FC = (props) => { const onChange = (e: React.ChangeEvent) => { props.onEditingNameUpdated(e.target.value); }; @@ -21,10 +21,9 @@ export const NameEditComponent = (props: Props) => { <> - + ); }; diff --git a/hooks/06_Enable/webpack.config.js b/hooks/06_Enable/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/06_Enable/webpack.config.js +++ b/hooks/06_Enable/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/07_ColorPicker/package.json b/hooks/07_ColorPicker/package.json index bfa0e4eb..264e71d2 100644 --- a/hooks/07_ColorPicker/package.json +++ b/hooks/07_ColorPicker/package.json @@ -15,27 +15,27 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/07_ColorPicker/src/components/colorBrowser.tsx b/hooks/07_ColorPicker/src/components/colorBrowser.tsx index 7dcc3905..813746ce 100644 --- a/hooks/07_ColorPicker/src/components/colorBrowser.tsx +++ b/hooks/07_ColorPicker/src/components/colorBrowser.tsx @@ -5,13 +5,11 @@ interface Props { color: Color; } -export const ColorBrowser = (props: Props) => { +export const ColorBrowser: React.FC = (props) => { const divStyle: React.CSSProperties = { width: "11rem", height: "7rem", - backgroundColor: `rgb(${props.color.red},${props.color.green}, ${ - props.color.blue - })` + backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})`, }; return
; diff --git a/hooks/07_ColorPicker/src/components/colorPicker.tsx b/hooks/07_ColorPicker/src/components/colorPicker.tsx index 91283505..23bdca3c 100644 --- a/hooks/07_ColorPicker/src/components/colorPicker.tsx +++ b/hooks/07_ColorPicker/src/components/colorPicker.tsx @@ -6,7 +6,7 @@ interface Props { onColorUpdated: (color: Color) => void; } -export const ColorPicker = (props: Props) => ( +export const ColorPicker: React.FC = (props) => (
{ +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/07_ColorPicker/src/components/nameEdit.tsx b/hooks/07_ColorPicker/src/components/nameEdit.tsx index 646a4d26..5461073e 100644 --- a/hooks/07_ColorPicker/src/components/nameEdit.tsx +++ b/hooks/07_ColorPicker/src/components/nameEdit.tsx @@ -8,7 +8,7 @@ interface Props { disabled : boolean; } -export const NameEditComponent = (props: Props) => { +export const NameEditComponent: React.FC = (props) => { const onChange = (e: React.ChangeEvent) => { props.onEditingNameUpdated(e.target.value); }; diff --git a/hooks/07_ColorPicker/webpack.config.js b/hooks/07_ColorPicker/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/07_ColorPicker/webpack.config.js +++ b/hooks/07_ColorPicker/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/08_ColorPickerRefactor/package.json b/hooks/08_ColorPickerRefactor/package.json index bfa0e4eb..264e71d2 100644 --- a/hooks/08_ColorPickerRefactor/package.json +++ b/hooks/08_ColorPickerRefactor/package.json @@ -15,27 +15,27 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/08_ColorPickerRefactor/src/components/colorBrowser.tsx b/hooks/08_ColorPickerRefactor/src/components/colorBrowser.tsx index 7dcc3905..a28e438e 100644 --- a/hooks/08_ColorPickerRefactor/src/components/colorBrowser.tsx +++ b/hooks/08_ColorPickerRefactor/src/components/colorBrowser.tsx @@ -5,7 +5,7 @@ interface Props { color: Color; } -export const ColorBrowser = (props: Props) => { +export const ColorBrowser: React.FC = (props) => { const divStyle: React.CSSProperties = { width: "11rem", height: "7rem", diff --git a/hooks/08_ColorPickerRefactor/src/components/colorPicker.tsx b/hooks/08_ColorPickerRefactor/src/components/colorPicker.tsx index 945ab4c1..ff4cbcdc 100644 --- a/hooks/08_ColorPickerRefactor/src/components/colorPicker.tsx +++ b/hooks/08_ColorPickerRefactor/src/components/colorPicker.tsx @@ -10,11 +10,11 @@ const updateColor = (props: Props, colorId: keyof Color) => (value: any) => { // keyof Color ensures only 'red', 'blue' or 'green' can be passed in. props.onColorUpdated({ ...props.color, // this creates a clone of the current props.color object... - [colorId]: value // ... which gets one of its properties (colorId) immediately replaced by a new value. + [colorId]: value, // ... which gets one of its properties (colorId) immediately replaced by a new value. }); }; -export const ColorPicker = (props: Props) => ( +export const ColorPicker: React.FC = (props) => (
void; } -const ColorSliderComponent = (props: PropsColorSlider) => { +const ColorSliderComponent: React.FC = (props: PropsColorSlider) => { return (
{ min="0" max="255" value={props.value} - onChange={event => props.onValueUpdated(+event.target.value)} + onChange={(event) => props.onValueUpdated(+event.target.value)} /> {props.value}
diff --git a/hooks/08_ColorPickerRefactor/src/components/hello.tsx b/hooks/08_ColorPickerRefactor/src/components/hello.tsx index 33a0ab73..b6b7aca4 100644 --- a/hooks/08_ColorPickerRefactor/src/components/hello.tsx +++ b/hooks/08_ColorPickerRefactor/src/components/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => { +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/08_ColorPickerRefactor/src/components/nameEdit.tsx b/hooks/08_ColorPickerRefactor/src/components/nameEdit.tsx index 646a4d26..7f9091e3 100644 --- a/hooks/08_ColorPickerRefactor/src/components/nameEdit.tsx +++ b/hooks/08_ColorPickerRefactor/src/components/nameEdit.tsx @@ -5,10 +5,10 @@ interface Props { editingName: string; onNameUpdated: () => any; onEditingNameUpdated: (newEditingName: string) => any; - disabled : boolean; + disabled: boolean; } -export const NameEditComponent = (props: Props) => { +export const NameEditComponent: React.FC = (props) => { const onChange = (e: React.ChangeEvent) => { props.onEditingNameUpdated(e.target.value); }; @@ -21,10 +21,9 @@ export const NameEditComponent = (props: Props) => { <> - + ); }; diff --git a/hooks/08_ColorPickerRefactor/webpack.config.js b/hooks/08_ColorPickerRefactor/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/08_ColorPickerRefactor/webpack.config.js +++ b/hooks/08_ColorPickerRefactor/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/09_Sidebar/Readme.md b/hooks/09_Sidebar/Readme.md index be7bddb2..e3dda427 100644 --- a/hooks/09_Sidebar/Readme.md +++ b/hooks/09_Sidebar/Readme.md @@ -89,9 +89,10 @@ _./webpack.config.js_ + { + loader: 'css-loader', + options: { -+ modules: true, -+ localIdentName: '[name]__[local]___[hash:base64:5]', -+ camelCase: true, ++ modules: { ++ localIdentName: "[name]__[local]___[hash:base64:5]", ++ }, ++ localsConvention: "camelCase", + }, + }, + ] diff --git a/hooks/09_Sidebar/package.json b/hooks/09_Sidebar/package.json index eadce68d..40171b2f 100644 --- a/hooks/09_Sidebar/package.json +++ b/hooks/09_Sidebar/package.json @@ -15,28 +15,28 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/node": "^11.9.4", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/node": "^14.0.24", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/09_Sidebar/src/components/colorBrowser.tsx b/hooks/09_Sidebar/src/components/colorBrowser.tsx index 7dcc3905..813746ce 100644 --- a/hooks/09_Sidebar/src/components/colorBrowser.tsx +++ b/hooks/09_Sidebar/src/components/colorBrowser.tsx @@ -5,13 +5,11 @@ interface Props { color: Color; } -export const ColorBrowser = (props: Props) => { +export const ColorBrowser: React.FC = (props) => { const divStyle: React.CSSProperties = { width: "11rem", height: "7rem", - backgroundColor: `rgb(${props.color.red},${props.color.green}, ${ - props.color.blue - })` + backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})`, }; return
; diff --git a/hooks/09_Sidebar/src/components/colorPicker.tsx b/hooks/09_Sidebar/src/components/colorPicker.tsx index ea449834..4346cd4d 100644 --- a/hooks/09_Sidebar/src/components/colorPicker.tsx +++ b/hooks/09_Sidebar/src/components/colorPicker.tsx @@ -6,15 +6,15 @@ interface Props { onColorUpdated: (color: Color) => void; } -const updateColor = (props: Props, colorId: keyof Color) => value => { +const updateColor = (props: Props, colorId: keyof Color) => (value) => { // keyof Color ensures only 'red', 'blue' or 'green' can be passed in. props.onColorUpdated({ ...props.color, // this creates a clone of the current props.color object... - [colorId]: value // ... which gets one of its properties (colorId) immediately replaced by a new value. + [colorId]: value, // ... which gets one of its properties (colorId) immediately replaced by a new value. }); }; -export const ColorPicker = (props: Props) => ( +export const ColorPicker: React.FC = (props) => (
void; } -const ColorSliderComponent = (props: PropsColorSlider) => { +const ColorSliderComponent: React.FC = (props) => { return (
{ min="0" max="255" value={props.value} - onChange={event => props.onValueUpdated(+event.target.value)} + onChange={(event) => props.onValueUpdated(+event.target.value)} /> {props.value}
diff --git a/hooks/09_Sidebar/src/components/hello.tsx b/hooks/09_Sidebar/src/components/hello.tsx index 33a0ab73..b6b7aca4 100644 --- a/hooks/09_Sidebar/src/components/hello.tsx +++ b/hooks/09_Sidebar/src/components/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => { +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/09_Sidebar/src/components/nameEdit.tsx b/hooks/09_Sidebar/src/components/nameEdit.tsx index 646a4d26..7f9091e3 100644 --- a/hooks/09_Sidebar/src/components/nameEdit.tsx +++ b/hooks/09_Sidebar/src/components/nameEdit.tsx @@ -5,10 +5,10 @@ interface Props { editingName: string; onNameUpdated: () => any; onEditingNameUpdated: (newEditingName: string) => any; - disabled : boolean; + disabled: boolean; } -export const NameEditComponent = (props: Props) => { +export const NameEditComponent: React.FC = (props) => { const onChange = (e: React.ChangeEvent) => { props.onEditingNameUpdated(e.target.value); }; @@ -21,10 +21,9 @@ export const NameEditComponent = (props: Props) => { <> - + ); }; diff --git a/hooks/09_Sidebar/src/components/sidebar.tsx b/hooks/09_Sidebar/src/components/sidebar.tsx index 762991eb..0847a552 100644 --- a/hooks/09_Sidebar/src/components/sidebar.tsx +++ b/hooks/09_Sidebar/src/components/sidebar.tsx @@ -7,13 +7,11 @@ interface Props { } const divStyle = (props: Props): React.CSSProperties => ({ - width: props.isVisible ? "23rem" : "0rem" + width: props.isVisible ? "23rem" : "0rem", }); -export const SidebarComponent: React.StatelessComponent = props => ( +export const SidebarComponent: React.StatelessComponent = (props) => (
{props.children}
); - - diff --git a/hooks/09_Sidebar/webpack.config.js b/hooks/09_Sidebar/webpack.config.js index eebdc585..c9996585 100644 --- a/hooks/09_Sidebar/webpack.config.js +++ b/hooks/09_Sidebar/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx", ".css"] + extensions: [".js", ".ts", ".tsx", ".css"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,13 +31,13 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, include: /node_modules/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.css$/, @@ -47,32 +47,34 @@ module.exports = { { loader: "css-loader", options: { - modules: true, - localIdentName: "[name]__[local]___[hash:base64:5]", - camelCase: true - } - } - ] + modules: { + localIdentName: "[name]__[local]___[hash:base64:5]", + }, + localsConvention: "camelCase", + }, + }, + ], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/10_TableMock/package.json b/hooks/10_TableMock/package.json index eadce68d..40171b2f 100644 --- a/hooks/10_TableMock/package.json +++ b/hooks/10_TableMock/package.json @@ -15,28 +15,28 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/node": "^11.9.4", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@types/node": "^14.0.24", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/10_TableMock/src/components/colorBrowser.tsx b/hooks/10_TableMock/src/components/colorBrowser.tsx index 7dcc3905..813746ce 100644 --- a/hooks/10_TableMock/src/components/colorBrowser.tsx +++ b/hooks/10_TableMock/src/components/colorBrowser.tsx @@ -5,13 +5,11 @@ interface Props { color: Color; } -export const ColorBrowser = (props: Props) => { +export const ColorBrowser: React.FC = (props) => { const divStyle: React.CSSProperties = { width: "11rem", height: "7rem", - backgroundColor: `rgb(${props.color.red},${props.color.green}, ${ - props.color.blue - })` + backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})`, }; return
; diff --git a/hooks/10_TableMock/src/components/colorPicker.tsx b/hooks/10_TableMock/src/components/colorPicker.tsx index ea449834..4346cd4d 100644 --- a/hooks/10_TableMock/src/components/colorPicker.tsx +++ b/hooks/10_TableMock/src/components/colorPicker.tsx @@ -6,15 +6,15 @@ interface Props { onColorUpdated: (color: Color) => void; } -const updateColor = (props: Props, colorId: keyof Color) => value => { +const updateColor = (props: Props, colorId: keyof Color) => (value) => { // keyof Color ensures only 'red', 'blue' or 'green' can be passed in. props.onColorUpdated({ ...props.color, // this creates a clone of the current props.color object... - [colorId]: value // ... which gets one of its properties (colorId) immediately replaced by a new value. + [colorId]: value, // ... which gets one of its properties (colorId) immediately replaced by a new value. }); }; -export const ColorPicker = (props: Props) => ( +export const ColorPicker: React.FC = (props) => (
void; } -const ColorSliderComponent = (props: PropsColorSlider) => { +const ColorSliderComponent: React.FC = (props) => { return (
{ min="0" max="255" value={props.value} - onChange={event => props.onValueUpdated(+event.target.value)} + onChange={(event) => props.onValueUpdated(+event.target.value)} /> {props.value}
diff --git a/hooks/10_TableMock/src/components/hello.tsx b/hooks/10_TableMock/src/components/hello.tsx index 33a0ab73..b6b7aca4 100644 --- a/hooks/10_TableMock/src/components/hello.tsx +++ b/hooks/10_TableMock/src/components/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => { +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/10_TableMock/src/components/nameEdit.tsx b/hooks/10_TableMock/src/components/nameEdit.tsx index 646a4d26..7f9091e3 100644 --- a/hooks/10_TableMock/src/components/nameEdit.tsx +++ b/hooks/10_TableMock/src/components/nameEdit.tsx @@ -5,10 +5,10 @@ interface Props { editingName: string; onNameUpdated: () => any; onEditingNameUpdated: (newEditingName: string) => any; - disabled : boolean; + disabled: boolean; } -export const NameEditComponent = (props: Props) => { +export const NameEditComponent: React.FC = (props) => { const onChange = (e: React.ChangeEvent) => { props.onEditingNameUpdated(e.target.value); }; @@ -21,10 +21,9 @@ export const NameEditComponent = (props: Props) => { <> - + ); }; diff --git a/hooks/10_TableMock/webpack.config.js b/hooks/10_TableMock/webpack.config.js index eebdc585..c9996585 100644 --- a/hooks/10_TableMock/webpack.config.js +++ b/hooks/10_TableMock/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx", ".css"] + extensions: [".js", ".ts", ".tsx", ".css"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,13 +31,13 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, include: /node_modules/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.css$/, @@ -47,32 +47,34 @@ module.exports = { { loader: "css-loader", options: { - modules: true, - localIdentName: "[name]__[local]___[hash:base64:5]", - camelCase: true - } - } - ] + modules: { + localIdentName: "[name]__[local]___[hash:base64:5]", + }, + localsConvention: "camelCase", + }, + }, + ], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/11_TableAxios/.babelrc b/hooks/11_TableAxios/.babelrc index 957cae3e..06770f7d 100644 --- a/hooks/11_TableAxios/.babelrc +++ b/hooks/11_TableAxios/.babelrc @@ -5,6 +5,9 @@ { "useBuiltIns": "entry" } - ] - ] + ], + "@babel/preset-typescript", + "@babel/preset-react" + ], + "plugins": ["@babel/plugin-transform-runtime"] } diff --git a/hooks/11_TableAxios/Readme.md b/hooks/11_TableAxios/Readme.md index 4808c875..7a5332d0 100644 --- a/hooks/11_TableAxios/Readme.md +++ b/hooks/11_TableAxios/Readme.md @@ -67,6 +67,31 @@ export const getMembersCollection = (): Promise => { + })); ``` +- Adding async await calling member api + +To make the async await work, we should install +- `npm install -D @babel/plugin-transform-runtime` +- `npm install -P @babel/runtime` + +and add to the .babelrc + +_.babelrc_ +```bash + "plugins": ["@babel/plugin-transform-runtime"] +``` + +_./src/components/memberTable.tsx_ +```diff +- const loadMemberCollection = () => { +- getMembersCollection().then(memberCollection => +- setMemberCollection(memberCollection) +- ); + ++ const loadMemberCollection = async () => { ++ const memberCollection = await getMembersCollection(); ++ setMemberCollection(memberCollection); +``` + - Aaaand... we don't need to add any update on the rest of the application, why? The function is providing the same contract, it returns a promise, let's give a try: diff --git a/hooks/11_TableAxios/package.json b/hooks/11_TableAxios/package.json index 7ef95a14..0d0d92b0 100644 --- a/hooks/11_TableAxios/package.json +++ b/hooks/11_TableAxios/package.json @@ -15,29 +15,33 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/node": "^11.9.4", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/plugin-transform-runtime": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@babel/preset-react": "^7.10.4", + "@babel/preset-typescript": "^7.10.4", + "@types/node": "^14.0.24", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "axios": "^0.18.0", - "react": "^16.8.2", - "react-dom": "^16.8.2" + "@babel/runtime": "^7.10.5", + "axios": "^0.19.2", + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/hooks/11_TableAxios/src/components/colorBrowser.tsx b/hooks/11_TableAxios/src/components/colorBrowser.tsx index 7dcc3905..813746ce 100644 --- a/hooks/11_TableAxios/src/components/colorBrowser.tsx +++ b/hooks/11_TableAxios/src/components/colorBrowser.tsx @@ -5,13 +5,11 @@ interface Props { color: Color; } -export const ColorBrowser = (props: Props) => { +export const ColorBrowser: React.FC = (props) => { const divStyle: React.CSSProperties = { width: "11rem", height: "7rem", - backgroundColor: `rgb(${props.color.red},${props.color.green}, ${ - props.color.blue - })` + backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})`, }; return
; diff --git a/hooks/11_TableAxios/src/components/colorPicker.tsx b/hooks/11_TableAxios/src/components/colorPicker.tsx index ea449834..4346cd4d 100644 --- a/hooks/11_TableAxios/src/components/colorPicker.tsx +++ b/hooks/11_TableAxios/src/components/colorPicker.tsx @@ -6,15 +6,15 @@ interface Props { onColorUpdated: (color: Color) => void; } -const updateColor = (props: Props, colorId: keyof Color) => value => { +const updateColor = (props: Props, colorId: keyof Color) => (value) => { // keyof Color ensures only 'red', 'blue' or 'green' can be passed in. props.onColorUpdated({ ...props.color, // this creates a clone of the current props.color object... - [colorId]: value // ... which gets one of its properties (colorId) immediately replaced by a new value. + [colorId]: value, // ... which gets one of its properties (colorId) immediately replaced by a new value. }); }; -export const ColorPicker = (props: Props) => ( +export const ColorPicker: React.FC = (props) => (
void; } -const ColorSliderComponent = (props: PropsColorSlider) => { +const ColorSliderComponent: React.FC = (props) => { return (
{ min="0" max="255" value={props.value} - onChange={event => props.onValueUpdated(+event.target.value)} + onChange={(event) => props.onValueUpdated(+event.target.value)} /> {props.value}
diff --git a/hooks/11_TableAxios/src/components/hello.tsx b/hooks/11_TableAxios/src/components/hello.tsx index 33a0ab73..b6b7aca4 100644 --- a/hooks/11_TableAxios/src/components/hello.tsx +++ b/hooks/11_TableAxios/src/components/hello.tsx @@ -4,6 +4,6 @@ interface Props { userName: string; } -export const HelloComponent = (props: Props) => { +export const HelloComponent: React.FC = (props) => { return

Hello user: {props.userName} !

; }; diff --git a/hooks/11_TableAxios/src/components/memberTable.tsx b/hooks/11_TableAxios/src/components/memberTable.tsx index eee44798..4ae64fa6 100644 --- a/hooks/11_TableAxios/src/components/memberTable.tsx +++ b/hooks/11_TableAxios/src/components/memberTable.tsx @@ -7,10 +7,9 @@ const useMemberCollection = () => { MemberEntity[] >([]); - const loadMemberCollection = () => { - getMembersCollection().then(memberCollection => - setMemberCollection(memberCollection) - ); + const loadMemberCollection = async () => { + const memberCollection = await getMembersCollection(); + setMemberCollection(memberCollection); }; return { memberCollection, loadMemberCollection }; @@ -34,7 +33,7 @@ export const MemberTableComponent = () => { - {memberCollection.map(member => ( + {memberCollection.map((member) => ( ))} diff --git a/hooks/11_TableAxios/src/components/nameEdit.tsx b/hooks/11_TableAxios/src/components/nameEdit.tsx index 646a4d26..7f9091e3 100644 --- a/hooks/11_TableAxios/src/components/nameEdit.tsx +++ b/hooks/11_TableAxios/src/components/nameEdit.tsx @@ -5,10 +5,10 @@ interface Props { editingName: string; onNameUpdated: () => any; onEditingNameUpdated: (newEditingName: string) => any; - disabled : boolean; + disabled: boolean; } -export const NameEditComponent = (props: Props) => { +export const NameEditComponent: React.FC = (props) => { const onChange = (e: React.ChangeEvent) => { props.onEditingNameUpdated(e.target.value); }; @@ -21,10 +21,9 @@ export const NameEditComponent = (props: Props) => { <> - + ); }; diff --git a/hooks/11_TableAxios/webpack.config.js b/hooks/11_TableAxios/webpack.config.js index eebdc585..c9996585 100644 --- a/hooks/11_TableAxios/webpack.config.js +++ b/hooks/11_TableAxios/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx", ".css"] + extensions: [".js", ".ts", ".tsx", ".css"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,13 +31,13 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, include: /node_modules/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.css$/, @@ -47,32 +47,34 @@ module.exports = { { loader: "css-loader", options: { - modules: true, - localIdentName: "[name]__[local]___[hash:base64:5]", - camelCase: true - } - } - ] + modules: { + localIdentName: "[name]__[local]___[hash:base64:5]", + }, + localsConvention: "camelCase", + }, + }, + ], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/12_ReactRouter/.babelrc b/hooks/12_ReactRouter/.babelrc index 957cae3e..d09d473e 100644 --- a/hooks/12_ReactRouter/.babelrc +++ b/hooks/12_ReactRouter/.babelrc @@ -5,6 +5,8 @@ { "useBuiltIns": "entry" } - ] + ], + "@babel/preset-typescript", + "@babel/preset-react" ] } diff --git a/hooks/12_ReactRouter/package.json b/hooks/12_ReactRouter/package.json index 2ff4e9af..fb377361 100644 --- a/hooks/12_ReactRouter/package.json +++ b/hooks/12_ReactRouter/package.json @@ -15,29 +15,32 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", - "@types/react-router-dom": "^4.3.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@babel/preset-react": "^7.10.4", + "@babel/preset-typescript": "^7.10.4", + "@types/node": "^14.0.24", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", + "@types/react-router-dom": "^5.1.5", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "react": "^16.8.2", - "react-dom": "^16.8.2", - "react-router-dom": "^4.3.1" + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0" } } diff --git a/hooks/12_ReactRouter/webpack.config.js b/hooks/12_ReactRouter/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/12_ReactRouter/webpack.config.js +++ b/hooks/12_ReactRouter/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/13_LoginForm/Readme.md b/hooks/13_LoginForm/Readme.md index b505d8ba..5cef8683 100644 --- a/hooks/13_LoginForm/Readme.md +++ b/hooks/13_LoginForm/Readme.md @@ -18,14 +18,14 @@ npm install - Let's update as well the name of the component. -_./src/pages/loginPage.tsx_ +_./src/pages/login.component.tsx_ ```diff import * as React from "react"; import { Link } from "react-router-dom"; - export const PageA = () => ( -+ export const LoginPage = () => ( ++ export const LoginComponent: React.FC = (props) => {
-

Hello from page A

+

Hello from login Page

@@ -43,7 +43,7 @@ _./src/app.tsx_ import * as React from "react"; import { HashRouter, Switch, Route } from "react-router-dom"; - import { PageA } from "./pages/pageA"; -+ import { LoginPage } from "./pages/loginPage"; ++ import { LoginComponent } from "./pages/login.component"; import { PageB } from "./pages/pageB"; export const App = () => { @@ -53,7 +53,7 @@ export const App = () => { - -+ ++ @@ -118,7 +118,7 @@ npm install @material-ui/core @material-ui/icons --save - Now we could create a login form it could look something like: -_./src/pages/loginPage.tsx_ +_./src/pages/login.container.tsx_ ```javascript import * as React from "react"; @@ -143,7 +143,7 @@ const useStyles = makeStyles(theme => interface Props extends RouteComponentProps {} -const LoginPageInner = (props: Props) => { +export const LoginContainer: React.FC = (props) => { const classes = useStyles(); return ( @@ -167,8 +167,6 @@ const LoginPageInner = (props: Props) => { ); }; - -export const LoginPage = withRouter < Props > LoginPageInner; ``` - This can be ok, but if we take a deeper look to this component, we could break down into two, one is the card itself the other the form dialog, so it should finally look like: @@ -186,10 +184,10 @@ export const LoginPage = withRouter < Props > LoginPageInner; - Let's create the LoginForm component (append it to the loginPage file): -_./src/pages/loginPage.tsx_ +_./src/pages/login.component.tsx_ ```javascript -const LoginForm = props => { +export const LoginComponent: React.FC = (props) => { return (
{ justifyContent: "center" }} > - - -
@@ -208,16 +216,16 @@ const LoginForm = props => { }; ``` -- And let's update the _loginPage.tsx_ +- And let's update the _login.container.tsx_ -_./src/pages/loginPage.tsx_ +_./src/pages/login.container.tsx_ ```diff return ( -+ ++ -
- interface Props extends RouteComponentProps {} -const LoginPageInner = (props) => { +export const LoginContainer: React.FC = (props) => { const { classes } = props; + const onLogin = () => { @@ -282,14 +290,13 @@ const LoginPageInner = (props) => { ) } -export const LoginPage = withRouter(LoginPageInner); ``` - Let's add the navigation on button clicked (later on we will check for user and pwd) _form.tsx_. In order to do this we have used react-router 4 "withRouter" HoC (High order component), and pass it down to the LoginForm component. -_./src/pages/loginPage.tsx_ +_./src/pages/login.component.tsx_ ```diff +interface PropsForm { @@ -351,62 +358,59 @@ _./src/api/login.ts_ import { LoginEntity } from "../model/login"; // Just a fake loginAPI -export const isValidLogin = (loginInfo: LoginEntity): boolean => - loginInfo.login === "admin" && loginInfo.password === "test"; +export const isValidLogin = (loginInfo: LoginEntity): Promise => + new Promise((resolve) => { + setTimeout(() => { + // mock call + resolve(loginInfo.login === "admin" && loginInfo.password === "test"); + }, 500); + }); ``` - Let's add the _api_ integration, plus navigation on login succeeded: - First let's create a login state and add the api integration. -_./src/pages/loginPage.tsx_ +_./src/pages/login.container.tsx_ ```diff + import { LoginEntity, createEmptyLogin } from '../model/login'; + import { isValidLogin } from '../api/login'; ``` -_./src/pages/loginPage.tsx_ +_./src/pages/login.container.tsx_ ```diff const LoginPageInner = (props: Props) => { -+ const [loginInfo, setLoginInfo] = React.useState(createEmptyLogin()); ++ const [loginInfo, setLoginInfo] = React.useState( ++ createEmptyLogin() ++ ); const { classes } = props; - const onLogin = () => { -+ if(isValidLogin(loginInfo)) { - props.history.push("/pageB"); -+ } + const loginSucceeded = (isValid: boolean) => { + if (isValid) { + history.push("/pageB"); + } else { + setShowAlert(true); + } + }; + + const handleLogin = (login: LoginEntity) => { + isValidLogin(login).then(loginSucceeded); }; ``` - Now let's read the data from the textfields components (user and password). -_./src/pages/loginPage.tsx_ +_./src/pages/login.container.tsx_ ```diff - const onLogin = () => { - if (isValidLogin(loginInfo)) { - props.history.push("/pageB"); - } - }; - -+ const onUpdateLoginField = (name, value) => { -+ setLoginInfo({ -+ ...loginInfo, -+ [name]: value, -+ }) -+ } - return ( - ++ ); @@ -414,23 +418,25 @@ _./src/pages/loginPage.tsx_ - And update _LoginForm_ props and textField onChange. -_./src/pages/loginPage.tsx_ +_./src/pages/login.component.tsx_ ```diff interface PropsForm { - onLogin: () => void; -+ onUpdateField: (name: string, value: any) => void; -+ loginInfo : LoginEntity; ++ onLogin: (login: LoginEntity) => void; } -const LoginForm = (props: PropsForm) => { -- const { onLogin } = props; -+ const { onLogin, onUpdateField, loginInfo } = props; - -+ // TODO: Enhacement move this outside the stateless component discuss why is a good idea +export const LoginComponent: React.FC = (props) => { ++ const [loginInfo, setLoginInfo] = React.useState( ++ const { onLogin } = props; ++ createEmptyLogin() ++ ); ++ const classes = useFormStyles(); + const onTexFieldChange = (fieldId) => (e) => { -+ onUpdateField(fieldId, e.target.value); -+ } ++ setLoginInfo({ ++ ...loginInfo, ++ [fieldId]: e.target.value, ++ }); ++ }; return (
{ justifyContent: "center" }} > - - -
@@ -458,13 +473,11 @@ const LoginForm = (props: PropsForm) => { - We will add material-ui classes to LoginForm component. -_./src/pages/loginPage.tsx_ +_./src/pages/login.container.tsx_ ```diff interface PropsForm { - onLogin: () => void; - onUpdateField: (name: string, value: any) => void; - loginInfo : LoginEntity; + onLogin: (login: LoginEntity) => void; } + // https://material-ui.com/styles/api/#makestyles-styles-options-hook @@ -478,14 +491,8 @@ interface PropsForm { + }) + ); -const LoginForm = (props: PropsForm) => { +export const LoginComponent: React.FC = (props) => { + const classes = useFormStyles(); - const { onLogin, onUpdateField, loginInfo } = props; - - // TODO: Enhacement move this outside the stateless component discuss why is a good idea - const onTexFieldChange = (fieldId) => (e) => { - onUpdateField(fieldId, e.target.value); - } return ( -
{ const [loginInfo, setLoginInfo] = React.useState( createEmptyLogin() ); -+ const [showLoginFailedMsg, setShowLoginFailedMsg] = React.useState(false); ++ const [isShowAlert, setShowAlert] = React.useState(false); const classes = useStyles(); const onLogin = () => { @@ -602,7 +609,7 @@ const LoginPageInner = (props: Props) => { props.history.push("/pageB"); - } + } else { -+ setShowLoginFailedMsg(true); ++ setShowAlert(true); + } } @@ -625,11 +632,11 @@ const LoginPageInner = (props: Props) => { /> -+ setShowLoginFailedMsg(false)} -+ /> ++ setShowAlert(false)} ++ /> + ); }; diff --git a/hooks/13_LoginForm/package.json b/hooks/13_LoginForm/package.json index d22ed28f..258ceb77 100644 --- a/hooks/13_LoginForm/package.json +++ b/hooks/13_LoginForm/package.json @@ -15,31 +15,34 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", - "@types/react-router-dom": "^4.3.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@babel/preset-react": "^7.10.4", + "@babel/preset-typescript": "^7.10.4", + "@types/node": "^14.0.24", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", + "@types/react-router-dom": "^5.1.5", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "@material-ui/core": "^4.0.1", - "@material-ui/icons": "^4.0.1", - "react": "^16.8.2", - "react-dom": "^16.8.2", - "react-router-dom": "^4.3.1" + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0" } } diff --git a/hooks/13_LoginForm/src/api/login.ts b/hooks/13_LoginForm/src/api/login.ts index 1f7d4f3b..6fd3e26c 100644 --- a/hooks/13_LoginForm/src/api/login.ts +++ b/hooks/13_LoginForm/src/api/login.ts @@ -1,5 +1,10 @@ -import {LoginEntity} from '../model/login'; +import { LoginEntity } from "../model/login"; // Just a fake loginAPI -export const isValidLogin = (loginInfo : LoginEntity) : boolean => - (loginInfo.login === 'admin' && loginInfo.password === 'test'); +export const isValidLogin = (loginInfo: LoginEntity): Promise => + new Promise((resolve) => { + setTimeout(() => { + // mock call + resolve(loginInfo.login === "admin" && loginInfo.password === "test"); + }, 500); + }); diff --git a/hooks/13_LoginForm/src/app.tsx b/hooks/13_LoginForm/src/app.tsx index 9c1b1a24..169d50d8 100644 --- a/hooks/13_LoginForm/src/app.tsx +++ b/hooks/13_LoginForm/src/app.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { HashRouter, Switch, Route } from "react-router-dom"; -import { LoginPage } from "./pages/loginPage"; +import { LoginContainer } from "./pages/login.container"; import { PageB } from "./pages/pageB"; export const App = () => { @@ -8,7 +8,7 @@ export const App = () => { <> - + diff --git a/hooks/13_LoginForm/src/common/notification.tsx b/hooks/13_LoginForm/src/common/notification.tsx index 3fc14394..50e2b706 100644 --- a/hooks/13_LoginForm/src/common/notification.tsx +++ b/hooks/13_LoginForm/src/common/notification.tsx @@ -11,29 +11,29 @@ interface Props { onClose: () => void; } -const useStyles = makeStyles(theme => +const useStyles = makeStyles((theme) => createStyles({ close: { - padding: theme.spacing(0.5) - } + padding: theme.spacing(0.5), + }, }) ); -export const NotificationComponent = (props: Props) => { +export const NotificationComponent: React.FC = (props) => { const classes = useStyles(); const { message, show, onClose } = props; return ( {message}} action={[ @@ -45,7 +45,7 @@ export const NotificationComponent = (props: Props) => { onClick={onClose} > - + , ]} /> ); diff --git a/hooks/13_LoginForm/src/pages/login.component.tsx b/hooks/13_LoginForm/src/pages/login.component.tsx new file mode 100644 index 00000000..c78a4851 --- /dev/null +++ b/hooks/13_LoginForm/src/pages/login.component.tsx @@ -0,0 +1,60 @@ +import * as React from "react"; +import makeStyles from "@material-ui/styles/makeStyles"; +import createStyles from "@material-ui/styles/createStyles"; +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { LoginEntity, createEmptyLogin } from "../model/login"; + +interface PropsForm { + onLogin: (login: LoginEntity) => void; +} + +// https://material-ui.com/styles/api/#makestyles-styles-options-hook +const useFormStyles = makeStyles((theme) => + createStyles({ + formContainer: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + }, + }) +); + +export const LoginComponent: React.FC = (props) => { + const { onLogin } = props; + const [loginInfo, setLoginInfo] = React.useState( + createEmptyLogin() + ); + const classes = useFormStyles(); + const onTexFieldChange = (fieldId) => (e) => { + setLoginInfo({ + ...loginInfo, + [fieldId]: e.target.value, + }); + }; + + return ( +
+ + + +
+ ); +}; diff --git a/hooks/13_LoginForm/src/pages/login.container.tsx b/hooks/13_LoginForm/src/pages/login.container.tsx new file mode 100644 index 00000000..943c433b --- /dev/null +++ b/hooks/13_LoginForm/src/pages/login.container.tsx @@ -0,0 +1,57 @@ +import * as React from "react"; +import { useHistory } from "react-router-dom"; +import { LoginEntity } from "../model/login"; +import { isValidLogin } from "../api/login"; +import { LoginComponent } from "./login.component"; +import Card from "@material-ui/core/Card"; +import CardHeader from "@material-ui/core/CardHeader"; +import CardContent from "@material-ui/core/CardContent"; +import createStyles from "@material-ui/styles/createStyles"; +import makeStyles from "@material-ui/styles/makeStyles"; +import { NotificationComponent } from "../common"; + +// https://material-ui.com/styles/api/#makestyles-styles-options-hook +const useFormStyles = makeStyles((theme) => + createStyles({ + card: { + maxWidth: 400, + margin: "0 auto", + }, + }) +); + +interface Props {} + +export const LoginContainer: React.FC = (props) => { + const history = useHistory(); + const [isShowAlert, setShowAlert] = React.useState(false); + const classes = useFormStyles(); + + const loginSucceeded = (isValid: boolean) => { + if (isValid) { + history.push("/pageB"); + } else { + setShowAlert(true); + } + }; + + const handleLogin = (login: LoginEntity) => { + isValidLogin(login).then(loginSucceeded); + }; + + return ( + <> + + + + + setShowAlert(false)} + /> + + + + ); +}; diff --git a/hooks/13_LoginForm/src/pages/loginPage.tsx b/hooks/13_LoginForm/src/pages/loginPage.tsx deleted file mode 100644 index 05e51a77..00000000 --- a/hooks/13_LoginForm/src/pages/loginPage.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import * as React from "react"; -import { withRouter, RouteComponentProps } from "react-router-dom"; -import makeStyles from "@material-ui/styles/makeStyles"; -import createStyles from "@material-ui/styles/createStyles"; -import Card from "@material-ui/core/Card"; -import CardHeader from "@material-ui/core/CardHeader"; -import CardContent from "@material-ui/core/CardContent"; -import TextField from "@material-ui/core/TextField"; -import Button from "@material-ui/core/Button"; -import { LoginEntity, createEmptyLogin } from "../model/login"; -import { isValidLogin } from "../api/login"; -import { NotificationComponent } from "../common"; - -// https://material-ui.com/styles/api/#makestyles-styles-options-hook -const useStyles = makeStyles(theme => - createStyles({ - card: { - maxWidth: 400, - margin: "0 auto" - } - }) -); - -interface Props extends RouteComponentProps {} - -const LoginPageInner = (props: Props) => { - const [loginInfo, setLoginInfo] = React.useState( - createEmptyLogin() - ); - const [showLoginFailedMsg, setShowLoginFailedMsg] = React.useState(false); - const classes = useStyles(); - - const onLogin = () => { - if (isValidLogin(loginInfo)) { - props.history.push("/pageB"); - } else { - setShowLoginFailedMsg(true); - } - }; - - const onUpdateLoginField = (name, value) => { - setLoginInfo({ - ...loginInfo, - [name]: value - }); - }; - - return ( - <> - - - - - - - setShowLoginFailedMsg(false)} - /> - - ); -}; - -export const LoginPage = withRouter(LoginPageInner); - -interface PropsForm { - onLogin: () => void; - onUpdateField: (name: string, value: any) => void; - loginInfo: LoginEntity; -} - -// https://material-ui.com/styles/api/#makestyles-styles-options-hook -const useFormStyles = makeStyles(theme => - createStyles({ - formContainer: { - display: "flex", - flexDirection: "column", - justifyContent: "center" - } - }) -); - -const LoginForm = (props: PropsForm) => { - const classes = useFormStyles(); - const { onLogin, onUpdateField, loginInfo } = props; - - // TODO: Enhacement move this outside the stateless component discuss why is a good idea - const onTexFieldChange = fieldId => e => { - onUpdateField(fieldId, e.target.value); - }; - - return ( -
- - - -
- ); -}; diff --git a/hooks/13_LoginForm/webpack.config.js b/hooks/13_LoginForm/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/13_LoginForm/webpack.config.js +++ b/hooks/13_LoginForm/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/14_FormValidation/Readme.md b/hooks/14_FormValidation/Readme.md index f906f597..f8538d50 100644 --- a/hooks/14_FormValidation/Readme.md +++ b/hooks/14_FormValidation/Readme.md @@ -2,11 +2,11 @@ Let's add validation support to this form. -For this we will use lc-form-validation library +For this we will use formik, fonk and fonk-formik library Summary steps: -- Install lc-form-validation library. +- Install formik, fonk, fonk-formik library. - Refactor input component to a common component and include error validation info. - Let's define the validation for the form. - Let's hook it. @@ -19,56 +19,40 @@ Summary steps: npm install ``` -- Let's install the _lc-form-validation-library_. +- Let's install the _formik, @lemoncode/fonk, @lemoncode/fonk-formik _. ```bash -npm install lc-form-validation --save +npm install formik @lemoncode/fonk @lemoncode/fonk-formik --save ``` - To avoid having too much repeated code let's move to common an input component, including it's label plus validation text. -_./common/textFieldForm.tsx_ +_./common/textField.component.tsx_ ```tsx import * as React from "react"; -import TextField from "@material-ui/core/TextField"; -import Typography from "@material-ui/core/Typography/Typography"; - -interface Props { - name: string; - label: string; - onChange: any; - value: string; - error?: string; - type?: string; -} - -const defaultProps: Partial = { - type: "text" -}; +import { useField } from "formik"; +import MuiTextField, { TextFieldProps } from "@material-ui/core/TextField"; -const onTextFieldChange = ( - fieldId: string, - onChange: (fieldId, value) => void -) => e => { - onChange(fieldId, e.target.value); -}; +export const TextFieldComponent: React.FC = (props) => { + const [field, meta] = useField(props.name); + const textFieldProps = Boolean(field) ? field : props; + const hasError = Boolean(meta && meta.touched && meta.error); -export const TextFieldForm: React.StatelessComponent = props => { - const { name, label, onChange, value, error, type } = props; return ( <> - - - {props.error} - ); }; @@ -80,200 +64,87 @@ _./src/common/index.ts_ ```diff export * from './notification'; -+ export * from './textFieldForm'; ++ export * from './TextFieldComponent'; ``` - Now let's define a basic validation for the form, we want to ensure both fields are informed. -_./src/pages/loginPage.validation.ts_ +_./src/pages/login.validation.ts_ ```typescript -import { - createFormValidation, - ValidationConstraints, - Validators -} from "lc-form-validation"; - -const loginFormValidationConstraints: ValidationConstraints = { - fields: { - login: [{ validator: Validators.required }], - password: [{ validator: Validators.required }] - } +import { ValidationSchema, Validators } from "@lemoncode/fonk"; +import { createFormikValidation } from "@lemoncode/fonk-formik"; + +const validationSchema: ValidationSchema = { + field: { + login: [Validators.required], + password: [Validators.required], + }, }; -export const loginFormValidation = createFormValidation( - loginFormValidationConstraints -); -``` - -- Let's create now a class to hold the dataFormErrors. - -_./src/pages/loginPage.viewmodel.ts_ - -```typescript -import { FieldValidationResult } from "lc-form-validation"; - -export interface LoginFormErrors { - login: FieldValidationResult; - password: FieldValidationResult; -} - -export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ - login: new FieldValidationResult(), - password: new FieldValidationResult() -}); +export const loginFormValidation = createFormikValidation(validationSchema); ``` - Now let's go for the component side. - First let's add the dataFormErrors to the state of the component. -_./src/pages/loginPage.tsx_ +_./src/pages/login.container.tsx_ ```diff import { isValidLogin } from "../api/login"; import { NotificationComponent } from "../common"; -+ import {LoginFormErrors, createDefaultLoginFormErrors} from './loginPage.viewmodel'; -``` - -_./src/pages/loginPage.tsx_ - -```diff -const LoginPageInner = (props: Props) => { - const [loginInfo, setLoginInfo] = React.useState( - createEmptyLogin() - ); -+ const [loginFormErrors, setLoginFormErrors] = React.useState(createDefaultLoginFormErrors()); - const [showLoginFailedMsg, setShowLoginFailedMsg] = React.useState(false); -``` - -- Let's fire the validation on viewmodel update. - -_./src/pages/loginPage.tsx_ - -```diff -+ import { loginFormValidation } from "./loginPage.validation"; -``` - -_./src/pages/loginPage.tsx_ - -```diff -const onUpdateLoginField = (name, value) => { - setLoginInfo({ - ...loginInfo, - [name]: value - }); - -+ loginFormValidation.validateField(loginInfo, name, value) -+ .then((fieldValidationResult) => { - -+ setLoginFormErrors({ -+ ...loginFormErrors, -+ [name]: fieldValidationResult, -+ }); -+ }); - }; ``` -- We need to pass down dataFormErrors - -_./src/pages/loginPage.tsx_ -```diff - -``` - -_./src/pages/loginPage.tsx_ - -```diff -interface PropsForm { - onLogin: () => void; - onUpdateField: (string, any) => void; - loginInfo: LoginEntity; -+ loginFormErrors : LoginFormErrors; -} -``` - -- Let's replace the _TextFieldForm_ entries with the wrapper we have created (includes +- Let's fire the validation on viewmodel update and replace the _TextFieldForm_ entries with the wrapper we have created (includes displaying validation errors). -_./src/pages/loginPage.tsx_ - -```diff -+ import { TextFieldForm } from '../common'; -``` - -_./src/pages/loginPage.tsx_ +_./src/pages/login.component.tsx_ ```diff -const LoginForm = (props: PropsForm) => { -- const { onLogin, onUpdateField, loginInfo } = props; -+ const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; -``` - -```diff -- -- ++ import { loginFormValidation } from "./login.validation"; ++ import { TextFieldComponent } from '../common'; + ++ ++ {() => ( ++
++
+- +- ++
++
++ )} ++
``` - Let's give a try -``` +```bash npm start ``` - And let's add an alert (Excercise and a notification) when the user clicks and the form all the fields are valid. -_./src/pages/loginPage.tsx_ - -```diff -const onLogin = () => { -+ loginFormValidation.validateForm(loginInfo) -+ .then((formValidationResult) => { -+ if(formValidationResult.succeeded) { - if (isValidLogin(loginInfo)) { - props.history.push("/pageB"); - } else { - setShowLoginFailedMsg(true); - } -+ } else { -+ alert('error, review the fields'); -+ const updatedLoginFormErrors = { -+ ...loginFormErrors, -+ ...formValidationResult.fieldErrors, -+ } -+ setLoginFormErrors(updatedLoginFormErrors); -+ } - - -+ }); -}; -``` - > Excercise, refactor this method following single abstraction level principle and single responsibility principle. # About Basefactor + Lemoncode diff --git a/hooks/14_FormValidation/package.json b/hooks/14_FormValidation/package.json index 2914eb69..79169511 100644 --- a/hooks/14_FormValidation/package.json +++ b/hooks/14_FormValidation/package.json @@ -15,32 +15,37 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", - "@types/react-router-dom": "^4.3.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@babel/preset-react": "^7.10.4", + "@babel/preset-typescript": "^7.10.4", + "@types/node": "^14.0.24", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", + "@types/react-router-dom": "^5.1.5", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "@material-ui/core": "^4.0.1", - "@material-ui/icons": "^4.0.1", - "lc-form-validation": "^2.0.0", - "react": "^16.8.2", - "react-dom": "^16.8.2", - "react-router-dom": "^4.3.1" + "@lemoncode/fonk": "^1.3.0", + "@lemoncode/fonk-formik": "^4.0.1", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "formik": "^2.1.5", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0" } } diff --git a/hooks/14_FormValidation/src/api/login.ts b/hooks/14_FormValidation/src/api/login.ts index 1f7d4f3b..6fd3e26c 100644 --- a/hooks/14_FormValidation/src/api/login.ts +++ b/hooks/14_FormValidation/src/api/login.ts @@ -1,5 +1,10 @@ -import {LoginEntity} from '../model/login'; +import { LoginEntity } from "../model/login"; // Just a fake loginAPI -export const isValidLogin = (loginInfo : LoginEntity) : boolean => - (loginInfo.login === 'admin' && loginInfo.password === 'test'); +export const isValidLogin = (loginInfo: LoginEntity): Promise => + new Promise((resolve) => { + setTimeout(() => { + // mock call + resolve(loginInfo.login === "admin" && loginInfo.password === "test"); + }, 500); + }); diff --git a/hooks/14_FormValidation/src/app.tsx b/hooks/14_FormValidation/src/app.tsx index 9c1b1a24..169d50d8 100644 --- a/hooks/14_FormValidation/src/app.tsx +++ b/hooks/14_FormValidation/src/app.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { HashRouter, Switch, Route } from "react-router-dom"; -import { LoginPage } from "./pages/loginPage"; +import { LoginContainer } from "./pages/login.container"; import { PageB } from "./pages/pageB"; export const App = () => { @@ -8,7 +8,7 @@ export const App = () => { <> - + diff --git a/hooks/14_FormValidation/src/common/index.ts b/hooks/14_FormValidation/src/common/index.ts index 2916465b..f377fb57 100644 --- a/hooks/14_FormValidation/src/common/index.ts +++ b/hooks/14_FormValidation/src/common/index.ts @@ -1,2 +1,2 @@ export * from './notification'; -export * from './textFieldForm'; \ No newline at end of file +export * from './textField.component'; \ No newline at end of file diff --git a/hooks/14_FormValidation/src/common/notification.tsx b/hooks/14_FormValidation/src/common/notification.tsx index 3fc14394..50e2b706 100644 --- a/hooks/14_FormValidation/src/common/notification.tsx +++ b/hooks/14_FormValidation/src/common/notification.tsx @@ -11,29 +11,29 @@ interface Props { onClose: () => void; } -const useStyles = makeStyles(theme => +const useStyles = makeStyles((theme) => createStyles({ close: { - padding: theme.spacing(0.5) - } + padding: theme.spacing(0.5), + }, }) ); -export const NotificationComponent = (props: Props) => { +export const NotificationComponent: React.FC = (props) => { const classes = useStyles(); const { message, show, onClose } = props; return ( {message}} action={[ @@ -45,7 +45,7 @@ export const NotificationComponent = (props: Props) => { onClick={onClose} > - + , ]} /> ); diff --git a/hooks/14_FormValidation/src/common/textField.component.tsx b/hooks/14_FormValidation/src/common/textField.component.tsx new file mode 100644 index 00000000..718e1173 --- /dev/null +++ b/hooks/14_FormValidation/src/common/textField.component.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import { useField } from "formik"; +import MuiTextField, { TextFieldProps } from "@material-ui/core/TextField"; + +export const TextFieldComponent: React.FC = (props) => { + const [field, meta] = useField(props.name); + const textFieldProps = Boolean(field) ? field : props; + const hasError = Boolean(meta && meta.touched && meta.error); + + return ( + <> + + + ); +}; diff --git a/hooks/14_FormValidation/src/common/textFieldForm.tsx b/hooks/14_FormValidation/src/common/textFieldForm.tsx deleted file mode 100644 index 221010ef..00000000 --- a/hooks/14_FormValidation/src/common/textFieldForm.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from "react"; -import TextField from "@material-ui/core/TextField"; -import Typography from "@material-ui/core/Typography/Typography"; - -interface Props { - name: string; - label: string; - onChange: any; - value: string; - error?: string; - type?: string; -} - -const defaultProps: Partial = { - type: "text" -}; - -const onTextFieldChange = ( - fieldId: string, - onChange: (fieldId, value) => void -) => e => { - onChange(fieldId, e.target.value); -}; - -export const TextFieldForm: React.StatelessComponent = props => { - const { name, label, onChange, value, error, type } = props; - return ( - <> - - - {props.error} - - - ); -}; diff --git a/hooks/14_FormValidation/src/pages/login.component.tsx b/hooks/14_FormValidation/src/pages/login.component.tsx new file mode 100644 index 00000000..e968d5d6 --- /dev/null +++ b/hooks/14_FormValidation/src/pages/login.component.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { LoginEntity, createEmptyLogin } from "../model/login"; +import { TextFieldComponent } from "../common"; +import { Form } from "formik"; +import createStyles from "@material-ui/styles/createStyles"; +import makeStyles from "@material-ui/styles/makeStyles"; +import Button from "@material-ui/core/Button"; +import { loginFormValidation } from "./login.validation"; +import { Formik } from "formik"; + +interface PropsForm { + onLogin: (login: LoginEntity) => void; +} + +// https://material-ui.com/styles/api/#makestyles-styles-options-hook +const useFormStyles = makeStyles((theme) => + createStyles({ + formContainer: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + }, + }) +); + +export const LoginComponent: React.FC = (props) => { + const classes = useFormStyles(); + const { onLogin } = props; + + return ( + + {() => ( +
+
+ + + +
+
+ )} +
+ ); +}; diff --git a/hooks/14_FormValidation/src/pages/login.container.tsx b/hooks/14_FormValidation/src/pages/login.container.tsx new file mode 100644 index 00000000..1da3ed87 --- /dev/null +++ b/hooks/14_FormValidation/src/pages/login.container.tsx @@ -0,0 +1,58 @@ +import * as React from "react"; +import { useHistory } from "react-router-dom"; +import { LoginEntity } from "../model/login"; +import { isValidLogin } from "../api/login"; +import { LoginComponent } from "./login.component"; +import Card from "@material-ui/core/Card"; +import CardHeader from "@material-ui/core/CardHeader"; +import CardContent from "@material-ui/core/CardContent"; +import createStyles from "@material-ui/styles/createStyles"; +import makeStyles from "@material-ui/styles/makeStyles"; +import { NotificationComponent } from "../common"; + +// https://material-ui.com/styles/api/#makestyles-styles-options-hook +const useFormStyles = makeStyles((theme) => + createStyles({ + card: { + maxWidth: 400, + margin: "0 auto", + }, + }) +); + +interface Props {} +interface Props {} + +export const LoginContainer: React.FC = (props) => { + const history = useHistory(); + const [isShowAlert, setShowAlert] = React.useState(false); + const classes = useFormStyles(); + + const loginSucceeded = (isValid: boolean) => { + if (isValid) { + history.push("/pageB"); + } else { + setShowAlert(true); + } + }; + + const handleLogin = (login: LoginEntity) => { + isValidLogin(login).then(loginSucceeded); + }; + + return ( + <> + + + + + setShowAlert(false)} + /> + + + + ); +}; diff --git a/hooks/14_FormValidation/src/pages/login.validation.ts b/hooks/14_FormValidation/src/pages/login.validation.ts new file mode 100644 index 00000000..20942129 --- /dev/null +++ b/hooks/14_FormValidation/src/pages/login.validation.ts @@ -0,0 +1,11 @@ +import { ValidationSchema, Validators } from "@lemoncode/fonk"; +import { createFormikValidation } from "@lemoncode/fonk-formik"; + +const validationSchema: ValidationSchema = { + field: { + login: [Validators.required], + password: [Validators.required], + }, +}; + +export const loginFormValidation = createFormikValidation(validationSchema); diff --git a/hooks/14_FormValidation/src/pages/loginPage.tsx b/hooks/14_FormValidation/src/pages/loginPage.tsx deleted file mode 100644 index 5dce227a..00000000 --- a/hooks/14_FormValidation/src/pages/loginPage.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import * as React from "react"; -import { Link } from "react-router-dom"; -import { withRouter, RouteComponentProps } from "react-router-dom"; -import makeStyles from "@material-ui/styles/makeStyles"; -import createStyles from "@material-ui/styles/createStyles"; -import Card from "@material-ui/core/Card"; -import CardHeader from "@material-ui/core/CardHeader"; -import CardContent from "@material-ui/core/CardContent"; -import TextField from "@material-ui/core/TextField"; -import Button from "@material-ui/core/Button"; -import { FormHelperText } from "@material-ui/core"; -import { LoginEntity, createEmptyLogin } from "../model/login"; -import { isValidLogin } from "../api/login"; -import { NotificationComponent } from "../common"; -import { - LoginFormErrors, - createDefaultLoginFormErrors -} from "./loginPage.viewmodel"; -import { loginFormValidation } from "./loginPage.validation"; -import { TextFieldForm } from "../common"; - -// https://material-ui.com/styles/api/#makestyles-styles-options-hook -const useStyles = makeStyles(theme => - createStyles({ - card: { - maxWidth: 400, - margin: "0 auto" - } - }) -); - -interface Props extends RouteComponentProps {} - -const LoginPageInner = (props: Props) => { - const [loginInfo, setLoginInfo] = React.useState( - createEmptyLogin() - ); - const [loginFormErrors, setLoginFormErrors] = React.useState( - createDefaultLoginFormErrors() - ); - const [showLoginFailedMsg, setShowLoginFailedMsg] = React.useState(false); - const classes = useStyles(); - - const onLogin = () => { - loginFormValidation.validateForm(loginInfo).then(formValidationResult => { - if (formValidationResult.succeeded) { - if (isValidLogin(loginInfo)) { - props.history.push("/pageB"); - } else { - setShowLoginFailedMsg(true); - } - } else { - alert("error, review the fields"); - const updatedLoginFormErrors = { - ...loginFormErrors, - ...formValidationResult.fieldErrors - }; - setLoginFormErrors(updatedLoginFormErrors); - } - }); - }; - - const onUpdateLoginField = (name, value) => { - setLoginInfo({ - ...loginInfo, - [name]: value - }); - - loginFormValidation - .validateField(loginInfo, name, value) - .then(fieldValidationResult => { - setLoginFormErrors({ - ...loginFormErrors, - [name]: fieldValidationResult - }); - }); - }; - - return ( - <> - - - - - - - setShowLoginFailedMsg(false)} - /> - - ); -}; - -export const LoginPage = withRouter(LoginPageInner); - -interface PropsForm { - onLogin: () => void; - onUpdateField: (string, any) => void; - loginInfo: LoginEntity; - loginFormErrors: LoginFormErrors; -} - -// https://material-ui.com/styles/api/#makestyles-styles-options-hook -const useFormStyles = makeStyles(theme => - createStyles({ - formContainer: { - display: "flex", - flexDirection: "column", - justifyContent: "center" - } - }) -); - -const LoginForm = (props: PropsForm) => { - const classes = useFormStyles(); - const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; - - // TODO: Enhacement move this outside the stateless component discuss why is a good idea - const onTexFieldChange = fieldId => e => { - onUpdateField(fieldId, e.target.value); - }; - - return ( -
- - - -
- ); -}; diff --git a/hooks/14_FormValidation/src/pages/loginPage.validation.ts b/hooks/14_FormValidation/src/pages/loginPage.validation.ts deleted file mode 100644 index ae379b5d..00000000 --- a/hooks/14_FormValidation/src/pages/loginPage.validation.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - createFormValidation, - ValidationConstraints, - Validators -} from "lc-form-validation"; - -const loginFormValidationConstraints: ValidationConstraints = { - fields: { - login: [{ validator: Validators.required }], - password: [{ validator: Validators.required }] - } -}; - -export const loginFormValidation = createFormValidation( - loginFormValidationConstraints -); diff --git a/hooks/14_FormValidation/src/pages/loginPage.viewmodel.ts b/hooks/14_FormValidation/src/pages/loginPage.viewmodel.ts deleted file mode 100644 index 4b3cf5b4..00000000 --- a/hooks/14_FormValidation/src/pages/loginPage.viewmodel.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FieldValidationResult } from "lc-form-validation"; - -export interface LoginFormErrors { - login: FieldValidationResult; - password: FieldValidationResult; -} - -export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ - login: new FieldValidationResult(), - password: new FieldValidationResult() -}); diff --git a/hooks/14_FormValidation/webpack.config.js b/hooks/14_FormValidation/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/14_FormValidation/webpack.config.js +++ b/hooks/14_FormValidation/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/hooks/15_Context/Readme.md b/hooks/15_Context/Readme.md index 38ef6245..fb20ed96 100644 --- a/hooks/15_Context/Readme.md +++ b/hooks/15_Context/Readme.md @@ -119,7 +119,7 @@ import { Link } from "react-router-dom"; - Let's update the loginPage. -_./src/pages/loginPage.tsx_ +_./src/pages/login.container.tsx_ ```diff - import { TextFieldForm } from "../common"; @@ -130,37 +130,25 @@ _./src/pages/loginPage.tsx_ _./src/pages/loginPage.tsx_ ```diff -const LoginPageInner = (props: Props) => { -+ const loginContext = React.useContext(SessionContext); +export const LoginContainer: React.FC = (props) => { ++ const loginContext = React.useContext(SessionContext); + const history = useHistory(); + const [isShowAlert, setShowAlert] = React.useState(false); + const classes = useFormStyles(); + + const loginSucceeded = (isValid: boolean, login: LoginEntity) => { + if (isValid) { + history.push("/pageB"); ++ loginContext.updateLogin(login.login); + } else { + setShowAlert(true); + } + }; - const [loginInfo, setLoginInfo] = React.useState( - createEmptyLogin() - ); - const [loginFormErrors, setLoginFormErrors] = React.useState( - createDefaultLoginFormErrors() - ); - const [showLoginFailedMsg, setShowLoginFailedMsg] = React.useState(false); - const classes = useStyles(); - - const onLogin = () => { - loginFormValidation.validateForm(loginInfo).then(formValidationResult => { - if (formValidationResult.succeeded) { - if (isValidLogin(loginInfo)) { - props.history.push("/pageB"); -+ loginContext.updateLogin(loginInfo.login); - } else { - setShowLoginFailedMsg(true); - } - } else { - alert("error, review the fields"); - const updatedLoginFormErrors = { - ...loginFormErrors, - ...formValidationResult.fieldErrors - }; - setLoginFormErrors(updatedLoginFormErrors); - } - }); + const handleLogin = (login: LoginEntity) => { + isValidLogin(login).then((isValid) => loginSucceeded(isValid, login)); }; +}; ``` - Let's give a try. diff --git a/hooks/15_Context/package.json b/hooks/15_Context/package.json index 2914eb69..79169511 100644 --- a/hooks/15_Context/package.json +++ b/hooks/15_Context/package.json @@ -15,32 +15,37 @@ "author": "Braulio Diez Botella", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", - "@babel/polyfill": "^7.2.5", - "@babel/preset-env": "^7.3.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.1", - "@types/react-router-dom": "^4.3.1", + "@babel/cli": "^7.10.5", + "@babel/core": "^7.10.5", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@babel/preset-react": "^7.10.4", + "@babel/preset-typescript": "^7.10.4", + "@types/node": "^14.0.24", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", + "@types/react-router-dom": "^5.1.5", "awesome-typescript-loader": "^5.2.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "file-loader": "^3.0.1", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.5.0", - "style-loader": "^0.23.1", - "typescript": "^3.3.3", - "url-loader": "^1.1.2", - "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.3.0", + "mini-css-extract-plugin": "^0.9.0", + "style-loader": "^1.2.1", + "typescript": "^3.9.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" }, "dependencies": { - "@material-ui/core": "^4.0.1", - "@material-ui/icons": "^4.0.1", - "lc-form-validation": "^2.0.0", - "react": "^16.8.2", - "react-dom": "^16.8.2", - "react-router-dom": "^4.3.1" + "@lemoncode/fonk": "^1.3.0", + "@lemoncode/fonk-formik": "^4.0.1", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "formik": "^2.1.5", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0" } } diff --git a/hooks/15_Context/src/api/login.ts b/hooks/15_Context/src/api/login.ts index 1f7d4f3b..6fd3e26c 100644 --- a/hooks/15_Context/src/api/login.ts +++ b/hooks/15_Context/src/api/login.ts @@ -1,5 +1,10 @@ -import {LoginEntity} from '../model/login'; +import { LoginEntity } from "../model/login"; // Just a fake loginAPI -export const isValidLogin = (loginInfo : LoginEntity) : boolean => - (loginInfo.login === 'admin' && loginInfo.password === 'test'); +export const isValidLogin = (loginInfo: LoginEntity): Promise => + new Promise((resolve) => { + setTimeout(() => { + // mock call + resolve(loginInfo.login === "admin" && loginInfo.password === "test"); + }, 500); + }); diff --git a/hooks/15_Context/src/app.tsx b/hooks/15_Context/src/app.tsx index 44bd6549..89ae2660 100644 --- a/hooks/15_Context/src/app.tsx +++ b/hooks/15_Context/src/app.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { HashRouter, Switch, Route } from "react-router-dom"; -import { LoginPage } from "./pages/loginPage"; +import { LoginContainer } from "./pages/login.container"; import { PageB } from "./pages/pageB"; import { SessionProvider } from "./common"; @@ -10,7 +10,7 @@ export const App = () => { - + diff --git a/hooks/15_Context/src/common/index.ts b/hooks/15_Context/src/common/index.ts index c6185541..feefdf2a 100644 --- a/hooks/15_Context/src/common/index.ts +++ b/hooks/15_Context/src/common/index.ts @@ -1,3 +1,3 @@ export * from "./notification"; -export * from "./textFieldForm"; +export * from "./textField.component"; export * from "./sessionContext"; diff --git a/hooks/15_Context/src/common/notification.tsx b/hooks/15_Context/src/common/notification.tsx index 3fc14394..50e2b706 100644 --- a/hooks/15_Context/src/common/notification.tsx +++ b/hooks/15_Context/src/common/notification.tsx @@ -11,29 +11,29 @@ interface Props { onClose: () => void; } -const useStyles = makeStyles(theme => +const useStyles = makeStyles((theme) => createStyles({ close: { - padding: theme.spacing(0.5) - } + padding: theme.spacing(0.5), + }, }) ); -export const NotificationComponent = (props: Props) => { +export const NotificationComponent: React.FC = (props) => { const classes = useStyles(); const { message, show, onClose } = props; return ( {message}} action={[ @@ -45,7 +45,7 @@ export const NotificationComponent = (props: Props) => { onClick={onClose} > - + , ]} /> ); diff --git a/hooks/15_Context/src/common/sessionContext.tsx b/hooks/15_Context/src/common/sessionContext.tsx index 4d1c0384..4edab12f 100644 --- a/hooks/15_Context/src/common/sessionContext.tsx +++ b/hooks/15_Context/src/common/sessionContext.tsx @@ -7,18 +7,18 @@ export interface SessionContextProps { export const createDefaultUser = (): SessionContextProps => ({ login: "no user", - updateLogin: value => { + updateLogin: (value) => { console.warn( "if you are reading this, likely you forgot to add the provider on top of your app" ); - } + }, }); export const SessionContext = React.createContext( createDefaultUser() ); -export const SessionProvider: React.FunctionComponent = props => { +export const SessionProvider: React.FC = (props) => { const [login, setLogin] = React.useState(""); return ( diff --git a/hooks/15_Context/src/common/textField.component.tsx b/hooks/15_Context/src/common/textField.component.tsx new file mode 100644 index 00000000..718e1173 --- /dev/null +++ b/hooks/15_Context/src/common/textField.component.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import { useField } from "formik"; +import MuiTextField, { TextFieldProps } from "@material-ui/core/TextField"; + +export const TextFieldComponent: React.FC = (props) => { + const [field, meta] = useField(props.name); + const textFieldProps = Boolean(field) ? field : props; + const hasError = Boolean(meta && meta.touched && meta.error); + + return ( + <> + + + ); +}; diff --git a/hooks/15_Context/src/common/textFieldForm.tsx b/hooks/15_Context/src/common/textFieldForm.tsx deleted file mode 100644 index 221010ef..00000000 --- a/hooks/15_Context/src/common/textFieldForm.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from "react"; -import TextField from "@material-ui/core/TextField"; -import Typography from "@material-ui/core/Typography/Typography"; - -interface Props { - name: string; - label: string; - onChange: any; - value: string; - error?: string; - type?: string; -} - -const defaultProps: Partial = { - type: "text" -}; - -const onTextFieldChange = ( - fieldId: string, - onChange: (fieldId, value) => void -) => e => { - onChange(fieldId, e.target.value); -}; - -export const TextFieldForm: React.StatelessComponent = props => { - const { name, label, onChange, value, error, type } = props; - return ( - <> - - - {props.error} - - - ); -}; diff --git a/hooks/15_Context/src/pages/login.component.tsx b/hooks/15_Context/src/pages/login.component.tsx new file mode 100644 index 00000000..2c4adb0e --- /dev/null +++ b/hooks/15_Context/src/pages/login.component.tsx @@ -0,0 +1,57 @@ +import * as React from "react"; +import { LoginEntity, createEmptyLogin } from "../model/login"; +import { TextFieldComponent } from "../common"; +import { Form } from "formik"; +import createStyles from "@material-ui/styles/createStyles"; +import makeStyles from "@material-ui/styles/makeStyles"; +import Button from "@material-ui/core/Button"; +import { loginFormValidation } from "./login.validation"; +import { Formik } from "formik"; + +interface PropsForm { + onLogin: (login: LoginEntity) => void; +} + +// https://material-ui.com/styles/api/#makestyles-styles-options-hook +const useFormStyles = makeStyles((theme) => + createStyles({ + formContainer: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + }, + card: { + maxWidth: 400, + margin: "0 auto", + }, + }) +); + +export const LoginComponent: React.FC = (props) => { + const classes = useFormStyles(); + const { onLogin } = props; + + return ( + + {() => ( +
+
+ + + +
+
+ )} +
+ ); +}; diff --git a/hooks/15_Context/src/pages/login.container.tsx b/hooks/15_Context/src/pages/login.container.tsx new file mode 100644 index 00000000..e3e65ca0 --- /dev/null +++ b/hooks/15_Context/src/pages/login.container.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import { useHistory } from "react-router-dom"; +import { LoginEntity } from "../model/login"; +import { isValidLogin } from "../api/login"; +import { LoginComponent } from "./login.component"; +import Card from "@material-ui/core/Card"; +import CardHeader from "@material-ui/core/CardHeader"; +import CardContent from "@material-ui/core/CardContent"; +import createStyles from "@material-ui/styles/createStyles"; +import makeStyles from "@material-ui/styles/makeStyles"; +import { NotificationComponent, SessionContext } from "../common"; + +// https://material-ui.com/styles/api/#makestyles-styles-options-hook +const useFormStyles = makeStyles((theme) => + createStyles({ + card: { + maxWidth: 400, + margin: "0 auto", + }, + }) +); + +interface Props {} + +export const LoginContainer: React.FC = (props) => { + const loginContext = React.useContext(SessionContext); + const history = useHistory(); + const [isShowAlert, setShowAlert] = React.useState(false); + const classes = useFormStyles(); + + const loginSucceeded = (isValid: boolean, login: LoginEntity) => { + if (isValid) { + history.push("/pageB"); + loginContext.updateLogin(login.login); + } else { + setShowAlert(true); + } + }; + + const handleLogin = (login: LoginEntity) => { + isValidLogin(login).then((isValid) => loginSucceeded(isValid, login)); + }; + + return ( + <> + + + + + setShowAlert(false)} + /> + + + + ); +}; diff --git a/hooks/15_Context/src/pages/login.validation.ts b/hooks/15_Context/src/pages/login.validation.ts new file mode 100644 index 00000000..20942129 --- /dev/null +++ b/hooks/15_Context/src/pages/login.validation.ts @@ -0,0 +1,11 @@ +import { ValidationSchema, Validators } from "@lemoncode/fonk"; +import { createFormikValidation } from "@lemoncode/fonk-formik"; + +const validationSchema: ValidationSchema = { + field: { + login: [Validators.required], + password: [Validators.required], + }, +}; + +export const loginFormValidation = createFormikValidation(validationSchema); diff --git a/hooks/15_Context/src/pages/loginPage.tsx b/hooks/15_Context/src/pages/loginPage.tsx deleted file mode 100644 index a2d10824..00000000 --- a/hooks/15_Context/src/pages/loginPage.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import * as React from "react"; -import { Link } from "react-router-dom"; -import { withRouter, RouteComponentProps } from "react-router-dom"; -import makeStyles from "@material-ui/styles/makestyles"; -import createStyles from "@material-ui/styles/createStyles"; -import Card from "@material-ui/core/Card"; -import CardHeader from "@material-ui/core/CardHeader"; -import CardContent from "@material-ui/core/CardContent"; -import TextField from "@material-ui/core/TextField"; -import Button from "@material-ui/core/Button"; -import { FormHelperText } from "@material-ui/core"; -import { LoginEntity, createEmptyLogin } from "../model/login"; -import { isValidLogin } from "../api/login"; -import { NotificationComponent } from "../common"; -import { - LoginFormErrors, - createDefaultLoginFormErrors -} from "./loginPage.viewmodel"; -import { loginFormValidation } from "./loginPage.validation"; -import { TextFieldForm, SessionContext } from "../common"; - -// https://material-ui.com/styles/api/#makestyles-styles-options-hook -const useStyles = makeStyles(theme => - createStyles({ - card: { - maxWidth: 400, - margin: "0 auto" - } - }) -); - -interface Props extends RouteComponentProps {} - -const LoginPageInner = (props: Props) => { - const loginContext = React.useContext(SessionContext); - - const [loginInfo, setLoginInfo] = React.useState( - createEmptyLogin() - ); - const [loginFormErrors, setLoginFormErrors] = React.useState( - createDefaultLoginFormErrors() - ); - const [showLoginFailedMsg, setShowLoginFailedMsg] = React.useState(false); - const classes = useStyles(); - - const onLogin = () => { - loginFormValidation.validateForm(loginInfo).then(formValidationResult => { - if (formValidationResult.succeeded) { - if (isValidLogin(loginInfo)) { - props.history.push("/pageB"); - loginContext.updateLogin(loginInfo.login); - } else { - setShowLoginFailedMsg(true); - } - } else { - alert("error, review the fields"); - const updatedLoginFormErrors = { - ...loginFormErrors, - ...formValidationResult.fieldErrors - }; - setLoginFormErrors(updatedLoginFormErrors); - } - }); - }; - - const onUpdateLoginField = (name, value) => { - setLoginInfo({ - ...loginInfo, - [name]: value - }); - - loginFormValidation - .validateField(loginInfo, name, value) - .then(fieldValidationResult => { - setLoginFormErrors({ - ...loginFormErrors, - [name]: fieldValidationResult - }); - }); - }; - - return ( - <> - - - - - - - setShowLoginFailedMsg(false)} - /> - - ); -}; - -export const LoginPage = withRouter(LoginPageInner); - -interface PropsForm { - onLogin: () => void; - onUpdateField: (string, any) => void; - loginInfo: LoginEntity; - loginFormErrors: LoginFormErrors; -} - -// https://material-ui.com/styles/api/#makestyles-styles-options-hook -const useFormStyles = makeStyles(theme => - createStyles({ - formContainer: { - display: "flex", - flexDirection: "column", - justifyContent: "center" - } - }) -); - -const LoginForm = (props: PropsForm) => { - const classes = useFormStyles(); - const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; - - // TODO: Enhacement move this outside the stateless component discuss why is a good idea - const onTexFieldChange = fieldId => e => { - onUpdateField(fieldId, e.target.value); - }; - - return ( -
- - - -
- ); -}; diff --git a/hooks/15_Context/src/pages/loginPage.validation.ts b/hooks/15_Context/src/pages/loginPage.validation.ts deleted file mode 100644 index ae379b5d..00000000 --- a/hooks/15_Context/src/pages/loginPage.validation.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - createFormValidation, - ValidationConstraints, - Validators -} from "lc-form-validation"; - -const loginFormValidationConstraints: ValidationConstraints = { - fields: { - login: [{ validator: Validators.required }], - password: [{ validator: Validators.required }] - } -}; - -export const loginFormValidation = createFormValidation( - loginFormValidationConstraints -); diff --git a/hooks/15_Context/src/pages/loginPage.viewmodel.ts b/hooks/15_Context/src/pages/loginPage.viewmodel.ts deleted file mode 100644 index 4b3cf5b4..00000000 --- a/hooks/15_Context/src/pages/loginPage.viewmodel.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FieldValidationResult } from "lc-form-validation"; - -export interface LoginFormErrors { - login: FieldValidationResult; - password: FieldValidationResult; -} - -export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ - login: new FieldValidationResult(), - password: new FieldValidationResult() -}); diff --git a/hooks/15_Context/src/pages/pageB.tsx b/hooks/15_Context/src/pages/pageB.tsx index af5c08a3..ccf75980 100644 --- a/hooks/15_Context/src/pages/pageB.tsx +++ b/hooks/15_Context/src/pages/pageB.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { Link } from "react-router-dom"; import { SessionContext } from "../common"; -export const PageB = () => { +export const PageB: React.FC = () => { const loginContext = React.useContext(SessionContext); return ( diff --git a/hooks/15_Context/webpack.config.js b/hooks/15_Context/webpack.config.js index 31ab3dba..32eff849 100644 --- a/hooks/15_Context/webpack.config.js +++ b/hooks/15_Context/webpack.config.js @@ -1,19 +1,19 @@ -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var MiniCssExtractPlugin = require("mini-css-extract-plugin"); -var webpack = require("webpack"); -var path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); +const path = require("path"); -var basePath = __dirname; +const basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: [".js", ".ts", ".tsx"] + extensions: [".js", ".ts", ".tsx"], }, entry: ["@babel/polyfill", "./index.tsx"], output: { path: path.join(basePath, "dist"), - filename: "bundle.js" + filename: "bundle.js", }, devtool: "source-map", devServer: { @@ -21,7 +21,7 @@ module.exports = { inline: true, // Enable watch and live reload host: "localhost", port: 8080, - stats: "errors-only" + stats: "errors-only", }, module: { rules: [ @@ -31,32 +31,33 @@ module.exports = { loader: "awesome-typescript-loader", options: { useBabel: true, - babelCore: "@babel/core" // needed for Babel v7 - } + babelCore: "@babel/core", // needed for Babel v7 + }, }, { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { - name: "assets/img/[name].[ext]?[hash]" - } - } - ] + name: "assets/img/[name].[ext]?[hash]", + esModule: false, + }, + }, + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: "index.html", //Name of file in ./dist/ template: "index.html", //Name of template in ./src - hash: true + hash: true, }), new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" - }) - ] + chunkFilename: "[id].css", + }), + ], }; diff --git a/readme.md b/readme.md index 7625f920..9036f076 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,16 @@ # React Typescript by sample +[🇪🇸 Versión Español](./readme_es.md) + The goal of this project is to provide a set of simple samples, providing and step by step guide to -start working with React and Typescript. +start working with React and TypeScript. We have incorporated a set of examples based on hooks. Right now you got two main folders: - [Hooks](./hooks): set of samples migrated to hooks (right now 15 samples migrated), if you are new to - React, or you are going to start working on a new project, I recommend you going through these + React, or you are going to start working on a new project, We recommend you going through these examples. - [Old_class_components_samples](./old_class_components_samples): The old samples, just in case you need to work with older react @@ -161,7 +163,7 @@ entity passed via props. ### 06 Handling asynchronous calls Members fake api replaced with async call to api github to retrieve list of -members of a given team. +members of a given organization. ### 07 Forms @@ -186,9 +188,11 @@ we will call async operations and fitting them into Redux architecture. ### 10 SpinnerAsync -Display a busy indicator when an ajax request is in progress. +Display a busy indicator while an ajax request is in progress. -To have a global count of promises gong on we are using [react-promise-tracker](https://github.com/Lemoncode/react-promise-tracker) and to display a cool spinner [react-spinner](https://github.com/davidhu2000/react-spinners) +To have a global count of promises gong on we are using +[react-promise-tracker](https://github.com/Lemoncode/react-promise-tracker) +and to display a cool spinner [react-spinner](https://github.com/davidhu2000/react-spinners) ### 11 Testing reducers @@ -228,8 +232,10 @@ Add a login page using Material-UI. # Contributors -Special thanks to [Jehu Sagardoy](https://github.com/jsagardoy) for his contributions checking -and getting uptodate examples. +Thank you very much to the contributors for keeping the project updated in all the examples. + +- [Jehu Sagardoy](https://github.com/jsagardoy) +- [Luis del Amo](https://github.com/delamux) # About Basefactor + Lemoncode @@ -239,4 +245,5 @@ We are an innovating team of Javascript experts, passionate about turning your i [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. -For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend +For the LATAM/Spanish audience we are running an Online Front End Master degree, +more info: [http://lemoncode.net/master-frontend](http://lemoncode.net/master-frontend) diff --git a/readme_es.md b/readme_es.md new file mode 100644 index 00000000..2107c97f --- /dev/null +++ b/readme_es.md @@ -0,0 +1,246 @@ +# React Typescript - ejemplos + +[🇬🇧 English version](./readme.md) + +El objetivo de este proyecto es ofrecer un set de ejemplos simples, proporcionando una guía paso a paso +para empezar a trabajar con React y TypeScript. + +Hemos incorporado un set de ejemplos basados en hooks. + +Ahora mismo hay dos carpetas principales: + +- [Hooks](./hooks): set de ejemplos migrados a hooks (ahora mismo 15 ejemplos), si eres nuevo en React, o vas a empezar a trabajar en +un nuevo proyecto, Te recomendamos que lo hagas a través de ´estos ejemplos. + +- [Old_class_components_samples](./old_class_components_samples): Los ejemplos viejos, sólo en caso de que necesite trabajar en un proyecto de versiones viejas de React o necesites mantener código legacy. + +Si quieres sumerjirte más en React Hooks puedes chequear este repo: [React Hooks By Example](https://github.com/Lemoncode/react-hooks-by-example) + +Otros repos guiados disponibles (React / Redux + TypeScript): + +- [Redux By Sample](https://github.com/Lemoncode/redux-by-sample) +- [Desde React a Redux](https://github.com/Lemoncode/from-react-to-redux-ts) +- [Redux Sagas](https://github.com/Lemoncode/redux-sagas-typescript-by-example) + +# Ejemplos + +Características: + +- Bundling basado en webpack. +- Basado en React + Typescript. +- Navegación simple usando react-router. +- Gestionando llamadas asíncronas y actualizaciones. +- Usando la librería Redux (todavía no está disponible para la version en Hooks, próximamente). +- Manejando llamadas asíncronas vía Redux-Thunk + Redux Saga (todavía no está disponible para la version en Hooks, próximamente). +- Añadiendo sporte para test unitarios (todavía no está disponible para la version en Hooks, próximamente). +- Implementando Lazy Loading (todavía no está disponible para la version en Hooks, próximamente). +- ... + +## Para empezar + +1. Instalar [NodeJS](http://www.nodejs.org) +2. Descarga este repo +3. Abre la consola de comandos que prefieras y haz 'cd' en el directorio de ejemplo dentro de este repo en tu máquina. +4. `npm install` - Instalación de los paquetes +5. `npm start` - Build del proyecto y lanza el servidor web (webpack-dev-server). +6. Copia y pega esta dirección en tu navegador [http://localhost:8080/](http://localhost:8080/) si ´éste no se abre automáticamente. + +# muestras + +## Hooks + +### [00 Boiler plate](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/00_BoilerPlate) + +Bundling + npm start basado en webpack. + +### [01 Hello React](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/01_HelloReact) + +Muestra el texto 'Hello React'. + +Hello world, muestra siemple de React render. + +### [02 Propiedades](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/02_Properties) + +Muestra el texto 'Hello {name}' (donde nombre es una propiedad que contiene el nombre). + +Introducción al concepto básico de React, manejando propiedades. + +### [03 State](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/03_State) + +Empezando desde la muestra 02, permite que el usuario cambie el nombre que se muestra. + +Introducción al concepto básico de React, manejando el Estado (State) usando Hooks. + +### [04 Callback](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/04_Callback) + +Empezando desde la nuestra 03, permite que el usario cambie la propiedad name +solo cuando presiona sobre el botón _change_ . + +Usando callbacks. + +### [05 Refactor](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/05_Refactor) + +Refactor de la muestra 04, Limpieza y discusión sobre dónde debe estar el estado. + +Reafactorizar el trabajo realizado. + +### [06 Enable](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/06_Enable) + +Empezando desde la nuestra 05, enable / disable del botón _change_ +cuando el texto está vacío o tiene el mismo nombre que originalmente,. + +Componentes Enable/disable + +### [07 ColorPicker](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/07_ColorPicker) + +demo simple de color picker (muestras cómo funcionan las propiedades). + +### [08 ColorPicker Refactor](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/08_ColorPickerRefactor) + +ColorPicker refactorizado. + +### [09 menú lateral](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/09_Sidebar) + +Simple implementación de un menú lateral. + +### [10 Tabla Mock](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/10_TableMock) + +Renderiza una tabla y usa un componente hijo para renderizar cada fila, usando un mock de datos. + +### [11 Tabla Axios](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/11_TableAxios) + +Empezando desde la nuestra 10, elimina el mock de datos, utiliza una REST API real (api Github), +utiliza axios para mejorar el rendimiento de la llamada fetch. + +### [12 React Router](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/12_ReactRouter) + +Empezando desde la nuestra 03, empezando a utilizar React-Router (navegación SPA). + +### [13 Formulario Login](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/13_LoginForm) + +Empezando desde la nuestra 12, implementa una página de login básica, +que redireccione al usuario a otra página cuando el login haya completado satisfactoriamente. + +### [14 Validación de formulario](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/14_FormValidation) + +Empezando desde la nuestra 13, añadir validaciones al formulario de login. + +### [15 Context](https://github.com/Lemoncode/react-typescript-samples/tree/master/hooks/15_Context) + +SEmpezando desde la nuestra 14, learn how React 16 context api works. +SEmpezando desde la nuestra 14, emprender cómo funciona la API de React 16 Context. + +## Carpeta class vieja + +### 00 Boiler plate + +Bundling + npm start basado en webpack. + +### 01 Hello React + +Hello world, muestra siemple de React render. + +### 02 Componentes + +Creando una cabecera común y una página 'about' con componentes de React. + +### 03 Navegación + +Creando una página de "miembros", añadir navegación usando react-router. + +### 04 Motrar datos + +Crear un componenente de lista de 'sólo lectura' (table >> tr >> td), +leer una lista de miembros desde una API falsa y añadirlos dentro del componente 'state' + +### 05 Prensentación de componentes + +Dividir el componente de lista en dos: componente de Lista y Fila, +pasar la entidad del miembro a través de las 'props' del componente. + +### 06 Manejando llamadas asíncronas + +Reemplazar la API false con una llamada asíncrona a la API de github y obtener la lista de miembros de una organización. + +### 07 Formularios + +En esta muestra añadiremos un link en la página de miembros que navegará hacia la 'página del miembro'. +Esta nueva página mostrará un formulario donde tendrás que introducir la url del avatar, el 'login' y el 'id' +del nuevo miembro (sólo suponiendo que podemos añadir esa info). + +### 08 Parámetros de navegación + Validaciones + +Editar un miembro seleccionado, aquí aprenderemos cómo añadir parámetros al link de navegación +y cómo obtenerlo desde el componente. + +La validación realizada hasta ahora: + +- Login: requerido, debe ser una cadena de texto (al menos 3 caracteres de longitud). + +### 09 Redux + +Añadido soporte para Redux, estado aislado en 'Redux reducers', implementa carga, guardar, +ciclo de validación simple. Esta muestra usa una API falsa, en las siguientes muentras +haremos llamadas a operaciones asíncronas y las ajustaremos dentro de la arquitectura de Redux. + +### 10 SpinnerAsync + +Muestra un indicador de carga mientras la petición ajax está en progreso. + +Para tener un recuento global de las promesas que estamos usando +[react-promise-tracker](https://github.com/Lemoncode/react-promise-tracker) +y mostras un bonito spinner [react-spinner](https://github.com/davidhu2000/react-spinners) + +### 11 Testeando reducers + +Muestra actualizada usando Jest. + +### 12 Testeando acciones + +Muestra actualizada usando Jest. + +### 13 Testeando componentes (Contenedores y presentaciones) + +Pendiente de actualizar Jest + Enzyme + +### 14 Reemplazando Redux Thunk con Redux Saga + +Pendiente de actualizar + +### 15 Lazy Loading y React-Router + +Pendiente de actualizar + +### 16 Añadir middlewares personalizados + +Pendiente de actualizar + +### 17 Añadir soporte para ReactHotloader y herramientas ReduxDev + +Pendiente de actualizar + +### 18 Hooks + +Replace class components by stateless components using Hooks. + +### 19 Formulario Login + +Añadir página de Login usando AMaterial-UI. + +# Colaboradores + +Muchas gracias a los colaborades por mantener el proyecto actualizado en todos los ejemplos. + +- [Jehu Sagardoy](https://github.com/jsagardoy) +- [Luis del Amo](https://github.com/delamux) + +# Sobre Basefactor + Lemoncode + +Somos un equipo innovador de expertos en JavaScript, apasionados en convertir tus ideas en productos robustos y consistentes. + +[Basefactor, consultoría por Lemoncode](http://www.basefactor.com) proporciona servicios de consultoría y servicios de orientación. + +[Lemoncode](http://lemoncode.net/services/en/#en-home) proporciona servicios de formación. + +Para la audiencia de LATAM/España estamos llevando a cabo un Master Online Front End, más info: +[http://lemoncode.net/master-frontend](http://lemoncode.net/master-frontend)