Browse code
Add basic implementation
Ed Langley authored on 08/05/2019 00:05:39
Showing 11 changed files
Showing 11 changed files
- .eslintrc
- .gitignore
- index.html
- package-lock.json
- package.json
- src/IpControl.js
- src/NameControl.js
- src/index.js
- src/react.js
- src/redux.js
- src/saga.js
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,28 @@ |
1 |
+{ |
|
2 |
+ "extends": [ |
|
3 |
+ "eslint:recommended", |
|
4 |
+ "plugin:react/recommended" |
|
5 |
+ ], |
|
6 |
+ "parser": "babel-eslint", |
|
7 |
+ "env": { |
|
8 |
+ "browser": true, |
|
9 |
+ "node": true, |
|
10 |
+ "jest": true, |
|
11 |
+ "es6": true |
|
12 |
+ }, |
|
13 |
+ "parserOptions": { |
|
14 |
+ "ecmaVersion": 2018, |
|
15 |
+ "sourceType": "module" |
|
16 |
+ }, |
|
17 |
+ "settings": { |
|
18 |
+ "react": { |
|
19 |
+ "version": "16.0" |
|
20 |
+ } |
|
21 |
+ }, |
|
22 |
+ "rules": { |
|
23 |
+ "no-unused-vars": ["error", { |
|
24 |
+ "argsIgnorePattern": "(^[_][_]*$)|(^.$)", |
|
25 |
+ "varsIgnorePattern": "(^[_][_]*$)|(^R$)" |
|
26 |
+ }] |
|
27 |
+ } |
|
28 |
+} |
... | ... |
@@ -1326,6 +1326,16 @@ |
1326 | 1326 |
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", |
1327 | 1327 |
"dev": true |
1328 | 1328 |
}, |
1329 |
+ "array-includes": { |
|
1330 |
+ "version": "3.0.3", |
|
1331 |
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", |
|
1332 |
+ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", |
|
1333 |
+ "dev": true, |
|
1334 |
+ "requires": { |
|
1335 |
+ "define-properties": "^1.1.2", |
|
1336 |
+ "es-abstract": "^1.7.0" |
|
1337 |
+ } |
|
1338 |
+ }, |
|
1329 | 1339 |
"array-unique": { |
1330 | 1340 |
"version": "0.3.2", |
1331 | 1341 |
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", |
... | ... |
@@ -1432,6 +1442,32 @@ |
1432 | 1442 |
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", |
1433 | 1443 |
"dev": true |
1434 | 1444 |
}, |
1445 |
+ "babel-eslint": { |
|
1446 |
+ "version": "10.0.1", |
|
1447 |
+ "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz", |
|
1448 |
+ "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==", |
|
1449 |
+ "dev": true, |
|
1450 |
+ "requires": { |
|
1451 |
+ "@babel/code-frame": "^7.0.0", |
|
1452 |
+ "@babel/parser": "^7.0.0", |
|
1453 |
+ "@babel/traverse": "^7.0.0", |
|
1454 |
+ "@babel/types": "^7.0.0", |
|
1455 |
+ "eslint-scope": "3.7.1", |
|
1456 |
+ "eslint-visitor-keys": "^1.0.0" |
|
1457 |
+ }, |
|
1458 |
+ "dependencies": { |
|
1459 |
+ "eslint-scope": { |
|
1460 |
+ "version": "3.7.1", |
|
1461 |
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", |
|
1462 |
+ "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", |
|
1463 |
+ "dev": true, |
|
1464 |
+ "requires": { |
|
1465 |
+ "esrecurse": "^4.1.0", |
|
1466 |
+ "estraverse": "^4.1.1" |
|
1467 |
+ } |
|
1468 |
+ } |
|
1469 |
+ } |
|
1470 |
+ }, |
|
1435 | 1471 |
"babel-runtime": { |
1436 | 1472 |
"version": "6.26.0", |
1437 | 1473 |
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", |
... | ... |
@@ -2960,6 +2996,32 @@ |
2960 | 2996 |
} |
2961 | 2997 |
} |
2962 | 2998 |
}, |
2999 |
+ "eslint-plugin-react": { |
|
3000 |
+ "version": "7.13.0", |
|
3001 |
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz", |
|
3002 |
+ "integrity": "sha512-uA5LrHylu8lW/eAH3bEQe9YdzpPaFd9yAJTwTi/i/BKTD7j6aQMKVAdGM/ML72zD6womuSK7EiGtMKuK06lWjQ==", |
|
3003 |
+ "dev": true, |
|
3004 |
+ "requires": { |
|
3005 |
+ "array-includes": "^3.0.3", |
|
3006 |
+ "doctrine": "^2.1.0", |
|
3007 |
+ "has": "^1.0.3", |
|
3008 |
+ "jsx-ast-utils": "^2.1.0", |
|
3009 |
+ "object.fromentries": "^2.0.0", |
|
3010 |
+ "prop-types": "^15.7.2", |
|
3011 |
+ "resolve": "^1.10.1" |
|
3012 |
+ }, |
|
3013 |
+ "dependencies": { |
|
3014 |
+ "doctrine": { |
|
3015 |
+ "version": "2.1.0", |
|
3016 |
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", |
|
3017 |
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", |
|
3018 |
+ "dev": true, |
|
3019 |
+ "requires": { |
|
3020 |
+ "esutils": "^2.0.2" |
|
3021 |
+ } |
|
3022 |
+ } |
|
3023 |
+ } |
|
3024 |
+ }, |
|
2963 | 3025 |
"eslint-scope": { |
2964 | 3026 |
"version": "4.0.3", |
2965 | 3027 |
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", |
... | ... |
@@ -4808,6 +4870,15 @@ |
4808 | 4870 |
"verror": "1.10.0" |
4809 | 4871 |
} |
4810 | 4872 |
}, |
4873 |
+ "jsx-ast-utils": { |
|
4874 |
+ "version": "2.1.0", |
|
4875 |
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz", |
|
4876 |
+ "integrity": "sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA==", |
|
4877 |
+ "dev": true, |
|
4878 |
+ "requires": { |
|
4879 |
+ "array-includes": "^3.0.3" |
|
4880 |
+ } |
|
4881 |
+ }, |
|
4811 | 4882 |
"kind-of": { |
4812 | 4883 |
"version": "6.0.2", |
4813 | 4884 |
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", |
... | ... |
@@ -5290,6 +5361,18 @@ |
5290 | 5361 |
"isobject": "^3.0.0" |
5291 | 5362 |
} |
5292 | 5363 |
}, |
5364 |
+ "object.fromentries": { |
|
5365 |
+ "version": "2.0.0", |
|
5366 |
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", |
|
5367 |
+ "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", |
|
5368 |
+ "dev": true, |
|
5369 |
+ "requires": { |
|
5370 |
+ "define-properties": "^1.1.2", |
|
5371 |
+ "es-abstract": "^1.11.0", |
|
5372 |
+ "function-bind": "^1.1.1", |
|
5373 |
+ "has": "^1.0.1" |
|
5374 |
+ } |
|
5375 |
+ }, |
|
5293 | 5376 |
"object.getownpropertydescriptors": { |
5294 | 5377 |
"version": "2.0.3", |
5295 | 5378 |
"resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", |
... | ... |
@@ -6114,6 +6197,12 @@ |
6114 | 6197 |
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", |
6115 | 6198 |
"dev": true |
6116 | 6199 |
}, |
6200 |
+ "prettier": { |
|
6201 |
+ "version": "1.17.0", |
|
6202 |
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", |
|
6203 |
+ "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", |
|
6204 |
+ "dev": true |
|
6205 |
+ }, |
|
6117 | 6206 |
"private": { |
6118 | 6207 |
"version": "0.1.8", |
6119 | 6208 |
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", |
... | ... |
@@ -6221,6 +6310,11 @@ |
6221 | 6310 |
"through2": "^2.0.0" |
6222 | 6311 |
} |
6223 | 6312 |
}, |
6313 |
+ "ramda": { |
|
6314 |
+ "version": "0.26.1", |
|
6315 |
+ "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", |
|
6316 |
+ "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" |
|
6317 |
+ }, |
|
6224 | 6318 |
"randombytes": { |
6225 | 6319 |
"version": "2.1.0", |
6226 | 6320 |
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", |
... | ... |
@@ -6257,6 +6351,17 @@ |
6257 | 6351 |
"scheduler": "^0.13.6" |
6258 | 6352 |
} |
6259 | 6353 |
}, |
6354 |
+ "react-dom": { |
|
6355 |
+ "version": "16.8.6", |
|
6356 |
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz", |
|
6357 |
+ "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==", |
|
6358 |
+ "requires": { |
|
6359 |
+ "loose-envify": "^1.1.0", |
|
6360 |
+ "object-assign": "^4.1.1", |
|
6361 |
+ "prop-types": "^15.6.2", |
|
6362 |
+ "scheduler": "^0.13.6" |
|
6363 |
+ } |
|
6364 |
+ }, |
|
6260 | 6365 |
"react-is": { |
6261 | 6366 |
"version": "16.8.6", |
6262 | 6367 |
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", |
... | ... |
@@ -9,13 +9,31 @@ |
9 | 9 |
"author": "", |
10 | 10 |
"license": "ISC", |
11 | 11 |
"devDependencies": { |
12 |
+ "babel-eslint": "^10.0.1", |
|
12 | 13 |
"eslint": "^5.16.0", |
13 |
- "parcel-bundler": "^1.12.3" |
|
14 |
+ "eslint-plugin-react": "^7.13.0", |
|
15 |
+ "parcel-bundler": "^1.12.3", |
|
16 |
+ "prettier": "^1.17.0" |
|
14 | 17 |
}, |
15 | 18 |
"dependencies": { |
19 |
+ "prop-types": "^15.7.2", |
|
20 |
+ "ramda": "^0.26.1", |
|
16 | 21 |
"react": "^16.8.6", |
22 |
+ "react-dom": "^16.8.6", |
|
17 | 23 |
"react-redux": "^7.0.3", |
18 | 24 |
"redux": "^4.0.1", |
19 | 25 |
"redux-saga": "^1.0.2" |
20 |
- } |
|
26 |
+ }, |
|
27 |
+ "rules": { |
|
28 |
+ "no-unused-vars": [ |
|
29 |
+ "error", |
|
30 |
+ { |
|
31 |
+ "argsIgnorePattern": "(^[_][_]*$)|(^.$)", |
|
32 |
+ "varsIgnorePattern": "(^[_][_]*$)|(^R$)" |
|
33 |
+ } |
|
34 |
+ ] |
|
35 |
+ }, |
|
36 |
+ "browserslist": [ |
|
37 |
+ "last 2 Chrome versions" |
|
38 |
+ ] |
|
21 | 39 |
} |
22 | 40 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,13 @@ |
1 |
+import React from "react"; |
|
2 |
+import PropTypes from "prop-types"; |
|
3 |
+ |
|
4 |
+export const IpControl = ({ ip, getIp }) => |
|
5 |
+ ip === "" ? ( |
|
6 |
+ <button onClick={getIp}>Get Ip</button> |
|
7 |
+ ) : ( |
|
8 |
+ <div>Your IP is: {ip}</div> |
|
9 |
+ ); |
|
10 |
+IpControl.propTypes = { |
|
11 |
+ ip: PropTypes.string, |
|
12 |
+ getIp: PropTypes.func |
|
13 |
+}; |
0 | 14 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,34 @@ |
1 |
+import React from "react"; |
|
2 |
+import PropTypes from "prop-types"; |
|
3 |
+ |
|
4 |
+export class NameControl extends React.Component { |
|
5 |
+ constructor(props) { |
|
6 |
+ super(props); |
|
7 |
+ this.state = { |
|
8 |
+ cur_input: "" |
|
9 |
+ }; |
|
10 |
+ } |
|
11 |
+ |
|
12 |
+ render() { |
|
13 |
+ if (this.props.name === "") { |
|
14 |
+ return ( |
|
15 |
+ <div> |
|
16 |
+ <input |
|
17 |
+ type="text" |
|
18 |
+ value={this.state.cur_input} |
|
19 |
+ onChange={e => this.setState({ cur_input: e.target.value })} |
|
20 |
+ /> |
|
21 |
+ <button onClick={() => this.props.updateName(this.state.cur_input)}> |
|
22 |
+ Set Name |
|
23 |
+ </button> |
|
24 |
+ </div> |
|
25 |
+ ); |
|
26 |
+ } else { |
|
27 |
+ return <div>Hello, {this.props.name}</div>; |
|
28 |
+ } |
|
29 |
+ } |
|
30 |
+} |
|
31 |
+NameControl.propTypes = { |
|
32 |
+ name: PropTypes.string, |
|
33 |
+ updateName: PropTypes.func |
|
34 |
+}; |
... | ... |
@@ -0,0 +1,48 @@ |
1 |
+import React from "react"; |
|
2 |
+import ReactDOM from "react-dom"; |
|
3 |
+import { Provider, connect } from "react-redux"; |
|
4 |
+import { createStore, applyMiddleware, compose } from "redux"; |
|
5 |
+import createSagaMiddleware from "redux-saga"; |
|
6 |
+ |
|
7 |
+import { Root } from "./react"; |
|
8 |
+import { rootReducer, updateName } from "./redux"; |
|
9 |
+import { rootSaga, getIp } from "./saga"; |
|
10 |
+ |
|
11 |
+// Store Setup ============================================================================== |
|
12 |
+ |
|
13 |
+// Make redux devtools work, if present |
|
14 |
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; |
|
15 |
+ |
|
16 |
+// Make the redux store, with saga middleware enabled |
|
17 |
+const sagaMiddleware = createSagaMiddleware(); |
|
18 |
+export const store = createStore( |
|
19 |
+ rootReducer, |
|
20 |
+ composeEnhancers(applyMiddleware(sagaMiddleware)) |
|
21 |
+); |
|
22 |
+ |
|
23 |
+// Run the root saga |
|
24 |
+sagaMiddleware.run(rootSaga); |
|
25 |
+ |
|
26 |
+// Connect Redux to Toplevel Component ====================================================== |
|
27 |
+const ConnectedRoot = connect( |
|
28 |
+ // Map the store's state to the toplevel component's props |
|
29 |
+ ({ name, ip }) => ({ name, ip }), |
|
30 |
+ // Map redux's dispatch function to props that will call it with a specific action |
|
31 |
+ dispatch => ({ |
|
32 |
+ getIp() { |
|
33 |
+ dispatch(getIp()); |
|
34 |
+ }, |
|
35 |
+ updateName(v) { |
|
36 |
+ dispatch(updateName(v)); |
|
37 |
+ } |
|
38 |
+ }) |
|
39 |
+)(Root); |
|
40 |
+ |
|
41 |
+// Render Connected Component to the dom at #root =========================================== |
|
42 |
+ReactDOM.render( |
|
43 |
+ // The Toplevel component is wrapped in a provider, to make the store available to connect |
|
44 |
+ <Provider store={store}> |
|
45 |
+ <ConnectedRoot /> |
|
46 |
+ </Provider>, |
|
47 |
+ document.getElementById("root") |
|
48 |
+); |
... | ... |
@@ -0,0 +1,17 @@ |
1 |
+import React from "react"; |
|
2 |
+import PropTypes from "prop-types"; |
|
3 |
+import { NameControl } from "./NameControl"; |
|
4 |
+import { IpControl } from "./IpControl"; |
|
5 |
+ |
|
6 |
+export const Root = ({ name, updateName, ip, getIp }) => ( |
|
7 |
+ <div> |
|
8 |
+ <NameControl name={name} updateName={updateName} /> |
|
9 |
+ <IpControl ip={ip} getIp={getIp} /> |
|
10 |
+ </div> |
|
11 |
+); |
|
12 |
+Root.propTypes = { |
|
13 |
+ ip: PropTypes.string, |
|
14 |
+ name: PropTypes.string, |
|
15 |
+ getIp: PropTypes.func, |
|
16 |
+ updateName: PropTypes.func |
|
17 |
+}; |
... | ... |
@@ -0,0 +1,22 @@ |
1 |
+const initialState = { |
|
2 |
+ name: "", |
|
3 |
+ ip: "" |
|
4 |
+}; |
|
5 |
+ |
|
6 |
+export const updateName = newName => { |
|
7 |
+ return { type: "UPDATE_NAME", data: newName }; |
|
8 |
+}; |
|
9 |
+ |
|
10 |
+export const updateIp = newIp => { |
|
11 |
+ return { type: "UPDATE_IP", data: newIp }; |
|
12 |
+}; |
|
13 |
+ |
|
14 |
+export const rootReducer = (state = initialState, action) => { |
|
15 |
+ if (action.type === "UPDATE_NAME") { |
|
16 |
+ return { ...state, name: action.data }; |
|
17 |
+ } else if (action.type === "UPDATE_IP") { |
|
18 |
+ return { ...state, ip: action.data }; |
|
19 |
+ } else { |
|
20 |
+ return state; |
|
21 |
+ } |
|
22 |
+}; |
... | ... |
@@ -0,0 +1,16 @@ |
1 |
+import { takeLatest, put } from "redux-saga/effects"; |
|
2 |
+import { updateIp } from "./redux"; |
|
3 |
+ |
|
4 |
+export function* rootSaga() { |
|
5 |
+ yield takeLatest("GET_IP", ipWorker); |
|
6 |
+} |
|
7 |
+ |
|
8 |
+export function getIp() { |
|
9 |
+ return { type: "GET_IP" }; |
|
10 |
+} |
|
11 |
+ |
|
12 |
+function* ipWorker() { |
|
13 |
+ const ipR = yield fetch("https://api.ipify.org"); |
|
14 |
+ const ip = yield ipR.text(); |
|
15 |
+ yield put(updateIp(ip)); |
|
16 |
+} |
|
0 | 17 |
\ No newline at end of file |