From c290eb89f696ebcebf715a2309e136d343436ba8 Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:05:15 +0300 Subject: [PATCH 01/14] Use actionCreators --- src/rdx/actions.ts | 18 ++++++++++++++++++ src/rdx/reducer/gameField.ts | 2 +- src/rdx/reducer/nextMove.ts | 2 +- src/rdx/types.ts | 2 -- src/screens/ReduxScreen.tsx | 8 +++----- 5 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 src/rdx/actions.ts delete mode 100644 src/rdx/types.ts diff --git a/src/rdx/actions.ts b/src/rdx/actions.ts new file mode 100644 index 00000000..731531ad --- /dev/null +++ b/src/rdx/actions.ts @@ -0,0 +1,18 @@ +export const X_MOVE = 'X_MOVE'; +export const O_MOVE = 'O_MOVE'; + +type Coordinates = { x: number; y: number }; + +export function xMove(payload: Coordinates) { + return { + type: X_MOVE, + payload, + } +} + +export function oMove(payload: Coordinates) { + return { + type: O_MOVE, + payload, + } +} \ No newline at end of file diff --git a/src/rdx/reducer/gameField.ts b/src/rdx/reducer/gameField.ts index 0a06fa70..643dc48c 100644 --- a/src/rdx/reducer/gameField.ts +++ b/src/rdx/reducer/gameField.ts @@ -1,5 +1,5 @@ import { Action } from "redux"; -import * as actionTypes from '@/rdx/types'; +import * as actionTypes from '@/rdx/actions'; type GameFieldState = string[][]; diff --git a/src/rdx/reducer/nextMove.ts b/src/rdx/reducer/nextMove.ts index c2c167c5..701951f0 100644 --- a/src/rdx/reducer/nextMove.ts +++ b/src/rdx/reducer/nextMove.ts @@ -1,5 +1,5 @@ import { Action } from "redux"; -import * as actionTypes from '@/rdx/types'; +import * as actionTypes from '@/rdx/actions'; type nextMoveState = 'x' | 'o'; diff --git a/src/rdx/types.ts b/src/rdx/types.ts deleted file mode 100644 index e0aa9dd4..00000000 --- a/src/rdx/types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const X_MOVE = 'X_MOVE'; -export const O_MOVE = 'O_MOVE'; \ No newline at end of file diff --git a/src/screens/ReduxScreen.tsx b/src/screens/ReduxScreen.tsx index 4e59dc9f..ec24778d 100644 --- a/src/screens/ReduxScreen.tsx +++ b/src/screens/ReduxScreen.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import * as actionTypes from '@/rdx/types'; import { Field } from '@/components/InteractiveField/components/Field'; import { withRedux } from '@/utils/withRedux'; import { Action } from 'redux'; import { NextMove } from 'components/NextMove'; import { TicTacToeGameState } from '@/rdx/reducer'; +import { xMove, oMove } from '@/rdx/actions'; function getReduxScreenState(state: TicTacToeGameState) { return { @@ -21,10 +21,8 @@ interface RawReduxScreenProps { class RawReduxScreen extends React.Component{ onCellClick = (x: number, y: number) => { - this.props.dispatch({ - type: this.props.nextMove === 'x' ? actionTypes.X_MOVE : actionTypes.O_MOVE, - payload: { x, y }, - }) + const action = this.props.nextMove === 'x' ? xMove : oMove; + this.props.dispatch(action({ x, y })); } render() { From 9bb49e8a0ba964cdef89fad3c307e2a130ef2692 Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:06:05 +0300 Subject: [PATCH 02/14] Install react-redux --- package-lock.json | 12 ++++++++++++ package.json | 1 + 2 files changed, 13 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0e8df0b4..c87476a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18146,6 +18146,18 @@ } } }, + "react-redux": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz", + "integrity": "sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==", + "requires": { + "@babel/runtime": "^7.5.5", + "hoist-non-react-statics": "^3.3.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.9.0" + } + }, "react-router": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", diff --git a/package.json b/package.json index bcbbbabe..1c4a13ee 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "ramda": "^0.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-redux": "^7.2.0", "react-router-dom": "^5.2.0", "redux": "^4.0.5" }, From 4582644741a2af863f1b7f00b91ad5242c5af7bd Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:06:58 +0300 Subject: [PATCH 03/14] npm audit fix --- package-lock.json | 183 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 93 insertions(+), 92 deletions(-) diff --git a/package-lock.json b/package-lock.json index c87476a6..f8f77c2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11827,9 +11827,9 @@ "dev": true }, "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "requires": { "eventemitter3": "^4.0.0", @@ -15035,9 +15035,9 @@ } }, "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", "dev": true }, "loglevelnext": { @@ -16829,9 +16829,9 @@ "dev": true }, "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", + "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", "dev": true, "requires": { "async": "^2.6.2", @@ -19788,13 +19788,25 @@ } }, "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", + "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", "dev": true, "requires": { "faye-websocket": "^0.10.0", - "uuid": "^3.0.1" + "uuid": "^3.4.0", + "websocket-driver": "0.6.5" + }, + "dependencies": { + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true, + "requires": { + "websocket-extensions": ">=0.1.1" + } + } } }, "sockjs-client": { @@ -19927,9 +19939,9 @@ "dev": true }, "spdy": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", - "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, "requires": { "debug": "^4.1.0", @@ -22329,9 +22341,9 @@ } }, "webpack-dev-server": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", - "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", + "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", "dev": true, "requires": { "ansi-html": "0.0.7", @@ -22342,65 +22354,71 @@ "debug": "^4.1.1", "del": "^4.1.1", "express": "^4.17.1", - "html-entities": "^1.2.1", + "html-entities": "^1.3.1", "http-proxy-middleware": "0.19.1", "import-local": "^2.0.0", "internal-ip": "^4.3.0", "ip": "^1.1.5", "is-absolute-url": "^3.0.3", "killable": "^1.0.1", - "loglevel": "^1.6.6", + "loglevel": "^1.6.8", "opn": "^5.5.0", "p-retry": "^3.0.1", - "portfinder": "^1.0.25", + "portfinder": "^1.0.26", "schema-utils": "^1.0.0", "selfsigned": "^1.10.7", "semver": "^6.3.0", "serve-index": "^1.9.1", - "sockjs": "0.3.19", + "sockjs": "0.3.20", "sockjs-client": "1.4.0", - "spdy": "^4.0.1", + "spdy": "^4.0.2", "strip-ansi": "^3.0.1", "supports-color": "^6.1.0", "url": "^0.11.0", "webpack-dev-middleware": "^3.7.2", "webpack-log": "^2.0.0", "ws": "^6.2.1", - "yargs": "12.0.5" + "yargs": "^13.3.2" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "html-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", "dev": true }, "import-local": { @@ -22419,12 +22437,6 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", @@ -22458,22 +22470,23 @@ "dev": true }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { + "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "strip-ansi": "^5.1.0" }, "dependencies": { "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -22488,33 +22501,23 @@ } }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -22529,29 +22532,27 @@ } }, "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" } }, "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/package.json b/package.json index 1c4a13ee..302b6037 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "typescript": "^3.8.3", "webpack": "^4.42.1", "webpack-cli": "^3.3.11", - "webpack-dev-server": "^3.10.3" + "webpack-dev-server": "^3.11.0" }, "dependencies": { "@emotion/core": "^10.0.28", From cf85dcb9db95bac77d05d00c7991893180097eb1 Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:10:12 +0300 Subject: [PATCH 04/14] Setup react-redux for ReduxScreen --- package-lock.json | 22 +++++++++++++ package.json | 1 + src/App.tsx | 62 ++++++++++++++++++++----------------- src/screens/ReduxScreen.tsx | 36 +++++++++++---------- 4 files changed, 75 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8f77c2d..b0ab8189 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4304,6 +4304,16 @@ "integrity": "sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw==", "dev": true }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz", @@ -4445,6 +4455,18 @@ "@types/react": "*" } }, + "@types/react-redux": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.9.tgz", + "integrity": "sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "@types/react-router": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.7.tgz", diff --git a/package.json b/package.json index 302b6037..e7add7e2 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@types/ramda": "^0.27.6", "@types/react": "^16.9.33", "@types/react-dom": "^16.9.6", + "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", "@types/react-test-renderer": "^16.9.2", "@typescript-eslint/eslint-plugin": "^2.25.0", diff --git a/src/App.tsx b/src/App.tsx index 78488da5..96fa954e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,35 +5,39 @@ import { FieldScreen } from "@/screens/FieldScreen"; import { NoMatchScreen } from "@/screens/NoMatchScreen"; import { UserScreen } from "@/screens/UserScreen"; import { ReduxScreen } from "@/screens/ReduxScreen"; +import { Provider } from "react-redux"; +import { store } from "@/rdx/store"; export const App: React.FC<{}> = () => ( - - - - - - - } /> - - - - - - - + + + + + + + + } /> + + + + + + + + ); diff --git a/src/screens/ReduxScreen.tsx b/src/screens/ReduxScreen.tsx index ec24778d..cb93a3f8 100644 --- a/src/screens/ReduxScreen.tsx +++ b/src/screens/ReduxScreen.tsx @@ -1,10 +1,10 @@ -import React from 'react'; -import { Field } from '@/components/InteractiveField/components/Field'; -import { withRedux } from '@/utils/withRedux'; -import { Action } from 'redux'; -import { NextMove } from 'components/NextMove'; -import { TicTacToeGameState } from '@/rdx/reducer'; -import { xMove, oMove } from '@/rdx/actions'; +import React from "react"; +import { Field } from "@/components/InteractiveField/components/Field"; +import { Action } from "redux"; +import { NextMove } from "components/NextMove"; +import { TicTacToeGameState } from "@/rdx/reducer"; +import { xMove, oMove } from "@/rdx/actions"; +import { connect } from "react-redux"; function getReduxScreenState(state: TicTacToeGameState) { return { @@ -19,20 +19,22 @@ interface RawReduxScreenProps { dispatch: (action: Action & { payload?: any }) => void; } -class RawReduxScreen extends React.Component{ +class RawReduxScreen extends React.Component { onCellClick = (x: number, y: number) => { - const action = this.props.nextMove === 'x' ? xMove : oMove; + const action = this.props.nextMove === "x" ? xMove : oMove; this.props.dispatch(action({ x, y })); - } + }; render() { - return
-

Open console to observe

- - -
{JSON.stringify(this.props, null, 2)}
-
+ return ( +
+

Open console to observe

+ + +
{JSON.stringify(this.props, null, 2)}
+
+ ); } } -export const ReduxScreen = withRedux(RawReduxScreen, getReduxScreenState); \ No newline at end of file +export const ReduxScreen = connect(getReduxScreenState)(RawReduxScreen); From 2fc1739256fc3fa3fd4a7e9e9675cda609e8dde1 Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:11:55 +0300 Subject: [PATCH 05/14] Apply connect to NextMove component --- src/components/NextMove.tsx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/NextMove.tsx b/src/components/NextMove.tsx index 33414ba3..0d2d7c57 100644 --- a/src/components/NextMove.tsx +++ b/src/components/NextMove.tsx @@ -1,9 +1,15 @@ -import React from 'react'; -import { withRedux } from '@/utils/withRedux'; -import { TicTacToeGameState } from '@/rdx/reducer'; +import React from "react"; +import { TicTacToeGameState } from "@/rdx/reducer"; +import { connect } from "react-redux"; -const RawNextMove: React.FC<{ nextMove: string }> = ({ nextMove }) =>

Next move is for {nextMove}

; +const RawNextMove: React.FC<{ nextMove: string }> = ({ nextMove }) => ( +

Next move is for {nextMove}

+); -export const NextMove = withRedux(RawNextMove, (state: TicTacToeGameState) => ({ - nextMove: state.nextMove -})); +function mapStateToProps(state: TicTacToeGameState) { + return { + nextMove: state.nextMove, + }; +} + +export const NextMove = connect(mapStateToProps)(RawNextMove); From d974d7c21fed069179381bfb18e18fbcecaf30d1 Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:12:38 +0300 Subject: [PATCH 06/14] Rename mapStateToProps for ReduxScreen --- src/screens/ReduxScreen.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/screens/ReduxScreen.tsx b/src/screens/ReduxScreen.tsx index cb93a3f8..e961a8bd 100644 --- a/src/screens/ReduxScreen.tsx +++ b/src/screens/ReduxScreen.tsx @@ -6,13 +6,6 @@ import { TicTacToeGameState } from "@/rdx/reducer"; import { xMove, oMove } from "@/rdx/actions"; import { connect } from "react-redux"; -function getReduxScreenState(state: TicTacToeGameState) { - return { - gameField: state.gameField, - nextMove: state.nextMove, - }; -} - interface RawReduxScreenProps { nextMove: string; gameField: string[][]; @@ -37,4 +30,11 @@ class RawReduxScreen extends React.Component { } } -export const ReduxScreen = connect(getReduxScreenState)(RawReduxScreen); +function mapStateToProps(state: TicTacToeGameState) { + return { + gameField: state.gameField, + nextMove: state.nextMove, + }; +} + +export const ReduxScreen = connect(mapStateToProps)(RawReduxScreen); From 7b5669bf27961f14e1147fc53d2683a90f7114ec Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:17:12 +0300 Subject: [PATCH 07/14] Use mapDispatchToProps for ReduxScreen --- src/rdx/actions.ts | 12 ++++++------ src/screens/ReduxScreen.tsx | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/rdx/actions.ts b/src/rdx/actions.ts index 731531ad..111ad756 100644 --- a/src/rdx/actions.ts +++ b/src/rdx/actions.ts @@ -1,18 +1,18 @@ -export const X_MOVE = 'X_MOVE'; -export const O_MOVE = 'O_MOVE'; +export const X_MOVE = "X_MOVE"; +export const O_MOVE = "O_MOVE"; -type Coordinates = { x: number; y: number }; +export type Coordinates = { x: number; y: number }; export function xMove(payload: Coordinates) { return { type: X_MOVE, payload, - } + }; } export function oMove(payload: Coordinates) { return { type: O_MOVE, payload, - } -} \ No newline at end of file + }; +} diff --git a/src/screens/ReduxScreen.tsx b/src/screens/ReduxScreen.tsx index e961a8bd..db3bad14 100644 --- a/src/screens/ReduxScreen.tsx +++ b/src/screens/ReduxScreen.tsx @@ -1,21 +1,21 @@ import React from "react"; import { Field } from "@/components/InteractiveField/components/Field"; -import { Action } from "redux"; +import { Action, Dispatch } from "redux"; import { NextMove } from "components/NextMove"; import { TicTacToeGameState } from "@/rdx/reducer"; -import { xMove, oMove } from "@/rdx/actions"; +import { Coordinates, xMove, oMove } from "@/rdx/actions"; import { connect } from "react-redux"; interface RawReduxScreenProps { nextMove: string; gameField: string[][]; - dispatch: (action: Action & { payload?: any }) => void; + xMove: (coords: Coordinates) => void; + oMove: (coords: Coordinates) => void; } class RawReduxScreen extends React.Component { onCellClick = (x: number, y: number) => { - const action = this.props.nextMove === "x" ? xMove : oMove; - this.props.dispatch(action({ x, y })); + this.props[this.props.nextMove ? "xMove" : "oMove"]({ x, y }); }; render() { @@ -37,4 +37,14 @@ function mapStateToProps(state: TicTacToeGameState) { }; } -export const ReduxScreen = connect(mapStateToProps)(RawReduxScreen); +function mapDispatchToProps(dispatch: Dispatch) { + return { + xMove: (coords: Coordinates) => dispatch(xMove(coords)), + oMove: (coords: Coordinates) => dispatch(oMove(coords)), + }; +} + +export const ReduxScreen = connect( + mapStateToProps, + mapDispatchToProps +)(RawReduxScreen); From c82c743e4b72ac579f96ae9c5e1a12740062bc74 Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:18:48 +0300 Subject: [PATCH 08/14] Use bindActionCreators for ReduxScreen --- src/screens/ReduxScreen.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/screens/ReduxScreen.tsx b/src/screens/ReduxScreen.tsx index db3bad14..450083cf 100644 --- a/src/screens/ReduxScreen.tsx +++ b/src/screens/ReduxScreen.tsx @@ -1,10 +1,11 @@ import React from "react"; import { Field } from "@/components/InteractiveField/components/Field"; -import { Action, Dispatch } from "redux"; +import { Dispatch } from "redux"; import { NextMove } from "components/NextMove"; import { TicTacToeGameState } from "@/rdx/reducer"; import { Coordinates, xMove, oMove } from "@/rdx/actions"; import { connect } from "react-redux"; +import { bindActionCreators } from "redux"; interface RawReduxScreenProps { nextMove: string; @@ -38,10 +39,7 @@ function mapStateToProps(state: TicTacToeGameState) { } function mapDispatchToProps(dispatch: Dispatch) { - return { - xMove: (coords: Coordinates) => dispatch(xMove(coords)), - oMove: (coords: Coordinates) => dispatch(oMove(coords)), - }; + return bindActionCreators({ xMove, oMove }, dispatch); } export const ReduxScreen = connect( From dc555149715f411086a1963b1f1c6600e571bbee Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:21:23 +0300 Subject: [PATCH 09/14] Fix action dispatching for ReduxScreen --- src/screens/ReduxScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/ReduxScreen.tsx b/src/screens/ReduxScreen.tsx index 450083cf..c4a757bc 100644 --- a/src/screens/ReduxScreen.tsx +++ b/src/screens/ReduxScreen.tsx @@ -16,7 +16,7 @@ interface RawReduxScreenProps { class RawReduxScreen extends React.Component { onCellClick = (x: number, y: number) => { - this.props[this.props.nextMove ? "xMove" : "oMove"]({ x, y }); + this.props[this.props.nextMove === "x" ? "xMove" : "oMove"]({ x, y }); }; render() { From 7e1967d5a069bb45cd20abcdec6fbe581141f7ae Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:22:02 +0300 Subject: [PATCH 10/14] Define mapDispatchToProps as object --- src/screens/ReduxScreen.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/screens/ReduxScreen.tsx b/src/screens/ReduxScreen.tsx index c4a757bc..7b51844c 100644 --- a/src/screens/ReduxScreen.tsx +++ b/src/screens/ReduxScreen.tsx @@ -1,11 +1,9 @@ import React from "react"; import { Field } from "@/components/InteractiveField/components/Field"; -import { Dispatch } from "redux"; import { NextMove } from "components/NextMove"; import { TicTacToeGameState } from "@/rdx/reducer"; import { Coordinates, xMove, oMove } from "@/rdx/actions"; import { connect } from "react-redux"; -import { bindActionCreators } from "redux"; interface RawReduxScreenProps { nextMove: string; @@ -38,9 +36,7 @@ function mapStateToProps(state: TicTacToeGameState) { }; } -function mapDispatchToProps(dispatch: Dispatch) { - return bindActionCreators({ xMove, oMove }, dispatch); -} +const mapDispatchToProps = { xMove, oMove }; export const ReduxScreen = connect( mapStateToProps, From 07787dd2491f64552157bca0a04dc30863eb028b Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:46:46 +0300 Subject: [PATCH 11/14] Basic test for connected component --- package-lock.json | 24 +++++++++++++++++++ package.json | 2 ++ src/screens/ReduxScreen.test.tsx | 40 ++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 src/screens/ReduxScreen.test.tsx diff --git a/package-lock.json b/package-lock.json index b0ab8189..ab21ff27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4515,6 +4515,15 @@ "@types/react": "*" } }, + "@types/redux-mock-store": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.0.2.tgz", + "integrity": "sha512-6LBtAQBN34i7SI5X+Qs4zpTEZO1tTDZ6sZ9fzFjYwTl3nLQXaBtwYdoV44CzNnyKu438xJ1lSIYyw0YMvunESw==", + "dev": true, + "requires": { + "redux": "^4.0.5" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -14921,6 +14930,12 @@ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", "dev": true }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -18558,6 +18573,15 @@ "symbol-observable": "^1.2.0" } }, + "redux-mock-store": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz", + "integrity": "sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==", + "dev": true, + "requires": { + "lodash.isplainobject": "^4.0.6" + } + }, "reflect.ownkeys": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", diff --git a/package.json b/package.json index e7add7e2..a74fab3a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", "@types/react-test-renderer": "^16.9.2", + "@types/redux-mock-store": "^1.0.2", "@typescript-eslint/eslint-plugin": "^2.25.0", "@typescript-eslint/parser": "^2.25.0", "babel-jest": "^25.2.4", @@ -75,6 +76,7 @@ "prettier": "^2.0.2", "react-docgen-typescript-loader": "^3.7.2", "react-test-renderer": "^16.13.1", + "redux-mock-store": "^1.5.4", "storybook-addon-react-docgen": "^1.2.32", "ts-node": "^8.8.2", "typescript": "^3.8.3", diff --git a/src/screens/ReduxScreen.test.tsx b/src/screens/ReduxScreen.test.tsx new file mode 100644 index 00000000..4679722b --- /dev/null +++ b/src/screens/ReduxScreen.test.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { ReduxScreen } from "./ReduxScreen"; +import configureStore from "redux-mock-store"; +import { Provider } from "react-redux"; +import { mount, shallow } from "enzyme"; + +const mockStore = configureStore([]); + +describe("ReduxScreen", () => { + let store: any; + + beforeEach(() => { + store = mockStore({ + nextMove: "x", + gameField: [[]], + }); + }); + + it("should generate action on click", () => { + const wrapper = mount( + + + + ); + + (wrapper.find("Field").props() as any).onClick(100, 999); + + expect(store.getActions()).toMatchInlineSnapshot(` + Array [ + Object { + "payload": Object { + "x": 100, + "y": 999, + }, + "type": "X_MOVE", + }, + ] + `); + }); +}); From 48fda7d14a86feb4a225c4c005a7ce7d7309f990 Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:53:06 +0300 Subject: [PATCH 12/14] Integration test for ReduxScreen with real store --- src/screens/ReduxScreen.test.tsx | 59 ++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/src/screens/ReduxScreen.test.tsx b/src/screens/ReduxScreen.test.tsx index 4679722b..f700ab98 100644 --- a/src/screens/ReduxScreen.test.tsx +++ b/src/screens/ReduxScreen.test.tsx @@ -1,19 +1,22 @@ import React from "react"; import { ReduxScreen } from "./ReduxScreen"; -import configureStore from "redux-mock-store"; import { Provider } from "react-redux"; -import { mount, shallow } from "enzyme"; - -const mockStore = configureStore([]); +import { mount } from "enzyme"; +import { reducer } from "@/rdx/reducer"; +import { createStore } from "redux"; describe("ReduxScreen", () => { let store: any; beforeEach(() => { - store = mockStore({ + store = createStore(reducer, { nextMove: "x", - gameField: [[]], + gameField: [ + ["", ""], + ["", ""], + ], }); + jest.spyOn(store, "dispatch"); }); it("should generate action on click", () => { @@ -23,17 +26,45 @@ describe("ReduxScreen", () => { ); - (wrapper.find("Field").props() as any).onClick(100, 999); + (wrapper.find("Field").props() as any).onClick(0, 1); + wrapper.update(); + (wrapper.find("Field").props() as any).onClick(1, 1); - expect(store.getActions()).toMatchInlineSnapshot(` + expect(store.getState()).toMatchInlineSnapshot(` + Object { + "gameField": Array [ + Array [ + "", + "", + ], + Array [ + "x", + "o", + ], + ], + "nextMove": "x", + } + `); + expect((store.dispatch as jest.Mock).mock.calls).toMatchInlineSnapshot(` Array [ - Object { - "payload": Object { - "x": 100, - "y": 999, + Array [ + Object { + "payload": Object { + "x": 0, + "y": 1, + }, + "type": "X_MOVE", + }, + ], + Array [ + Object { + "payload": Object { + "x": 1, + "y": 1, + }, + "type": "O_MOVE", }, - "type": "X_MOVE", - }, + ], ] `); }); From 3122c49362f9e4f5a982302a18c2c7547e1b004a Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 17:57:39 +0300 Subject: [PATCH 13/14] Keep both optoins together --- src/screens/ReduxScreen.test.tsx | 40 ++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/screens/ReduxScreen.test.tsx b/src/screens/ReduxScreen.test.tsx index f700ab98..0b2b0ef6 100644 --- a/src/screens/ReduxScreen.test.tsx +++ b/src/screens/ReduxScreen.test.tsx @@ -4,8 +4,44 @@ import { Provider } from "react-redux"; import { mount } from "enzyme"; import { reducer } from "@/rdx/reducer"; import { createStore } from "redux"; +import configureStore from "redux-mock-store"; -describe("ReduxScreen", () => { +describe("ReduxScreen with mocked store", () => { + const mockStore = configureStore([]); + + let store: any; + + beforeEach(() => { + store = mockStore({ + nextMove: "x", + gameField: [[]], + }); + }); + + it("should generate action on click", () => { + const wrapper = mount( + + + + ); + + (wrapper.find("Field").props() as any).onClick(100, 999); + + expect(store.getActions()).toMatchInlineSnapshot(` + Array [ + Object { + "payload": Object { + "x": 100, + "y": 999, + }, + "type": "X_MOVE", + }, + ] + `); + }); +}); + +describe("ReduxScreen with real store", () => { let store: any; beforeEach(() => { @@ -27,7 +63,7 @@ describe("ReduxScreen", () => { ); (wrapper.find("Field").props() as any).onClick(0, 1); - wrapper.update(); + wrapper.update(); // we need this if we're going to compare snapshots (wrapper.find("Field").props() as any).onClick(1, 1); expect(store.getState()).toMatchInlineSnapshot(` From c08462750fdf685b4a8f293fc98705ba3d0042b0 Mon Sep 17 00:00:00 2001 From: Vasil Vanchuk Date: Mon, 25 May 2020 19:16:39 +0300 Subject: [PATCH 14/14] Update README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 468e542d..bdb99896 100644 --- a/README.md +++ b/README.md @@ -154,3 +154,10 @@ React Patterns [Pull request](https://github.com/nickovchinnikov/react-js-tutorial/pull/23)
[Presentation](https://drive.google.com/file/d/1VezGwpkXUV38X-pL48j9ZjC58ASPuTPl/view?usp=sharing)
+ + +## Lesson 16: +* React + Redux + +[Pull request](https://github.com/nickovchinnikov/react-js-tutorial/pull/27)
+[Presentation](https://docs.google.com/presentation/d/1kng-DBHU91jQqjWHQVOMNMZjjfe8zawS8zzLP44TnuM/edit?usp=sharing)