diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/package-lock.json b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/package-lock.json
index 684e33db1..5557870f0 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/package-lock.json
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/package-lock.json
@@ -8,13 +8,15 @@
"name": "react-redux-basic",
"version": "0.0.0",
"dependencies": {
- "@reduxjs/toolkit": "^2.5.0",
+ "@reduxjs/toolkit": "^2.6.0",
+ "axios": "^1.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/axios": "^0.9.36",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-redux": "^7.1.34",
@@ -1061,11 +1063,13 @@
}
},
"node_modules/@reduxjs/toolkit": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz",
- "integrity": "sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.7.0.tgz",
+ "integrity": "sha512-XVwolG6eTqwV0N8z/oDlN93ITCIGIop6leXlGJI/4EKy+0POYkR+ABHRSdGXY+0MQvJBP8yAzh+EYFxTuvmBiQ==",
"license": "MIT",
"dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@standard-schema/utils": "^0.3.0",
"immer": "^10.0.3",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
@@ -1350,6 +1354,25 @@
"win32"
]
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/axios": {
+ "version": "0.9.36",
+ "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.9.36.tgz",
+ "integrity": "sha512-NLOpedx9o+rxo/X5ChbdiX6mS1atE4WHmEEIcR9NLenRVa5HoVjAvjafwU3FPTqnZEstpoqCaW7fagqSoTDNeg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1779,6 +1802,23 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
+ "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1843,6 +1883,19 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1911,6 +1964,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1972,6 +2037,29 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.80",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.80.tgz",
@@ -1979,6 +2067,51 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/esbuild": {
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
@@ -2345,6 +2478,41 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2360,6 +2528,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -2370,6 +2547,43 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2396,6 +2610,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2413,6 +2639,45 @@
"node": ">=8"
}
},
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -2645,6 +2910,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2669,6 +2943,27 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2864,6 +3159,12 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/package.json b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/package.json
index f78dda4e1..f36fe2cad 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/package.json
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/package.json
@@ -10,13 +10,15 @@
"preview": "vite preview"
},
"dependencies": {
- "@reduxjs/toolkit": "^2.5.0",
+ "@reduxjs/toolkit": "^2.6.0",
+ "axios": "^1.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/axios": "^0.9.36",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-redux": "^7.1.34",
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/App.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/App.tsx
index 77b9fa138..c0008304f 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/App.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/App.tsx
@@ -1,13 +1,15 @@
import React from 'react';
import Counter from './components/counter';
import CounterAsync from './components/counterAsync';
+import CurrencyRates from './components/currencyRatesAdvanced';
const App: React.FC = () => {
return (
Redux + React
- {/* */}
-
+
+ {/* */}
+ {/* */}
);
};
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/counter.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/counter.tsx
index 49c002192..e840c605a 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/counter.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/counter.tsx
@@ -9,7 +9,7 @@ const Counter: React.FC = () => {
return (
-
{count}
+
Счетчик: {count}
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/counterAsync.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/counterAsync.tsx
index 8d97c900c..d674f7dac 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/counterAsync.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/counterAsync.tsx
@@ -1,19 +1,14 @@
-import React, { useEffect } from 'react';
+import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from '../store/store';
import { increment, decrement, incrementByAmount, fetchRandomNumber } from '../store/counterSliceAsync';
const Counter: React.FC = () => {
- const count = useSelector((state: RootState) => state.counter.value);
- const status = useSelector((state: RootState) => state.counter.status);
- const error = useSelector((state: RootState) => state.counter.error);
+ const count = useSelector((state: RootState) => state.counterAsync.value);
+ const status = useSelector((state: RootState) => state.counterAsync.status);
+ const error = useSelector((state: RootState) => state.counterAsync.error);
const dispatch: AppDispatch = useDispatch();
-
- // Загружаем случайное число при монтировании компонента с помощью useEffect
- useEffect(() => {
- dispatch(fetchRandomNumber());
- }, [dispatch]);
-
+
return (
Счетчик: {count}
@@ -25,7 +20,7 @@ const Counter: React.FC = () => {
{/* Кнопка для повторной загрузки случайного числа */}
-
+
);
};
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/currencyRatesAdvanced.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/currencyRatesAdvanced.tsx
new file mode 100644
index 000000000..106bab83b
--- /dev/null
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/components/currencyRatesAdvanced.tsx
@@ -0,0 +1,57 @@
+// CurrencyRates.tsx
+import React, { useEffect, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { fetchCurrencyRates, setBaseCurrency } from '../store/currencySlice';
+import { RootState, AppDispatch } from '../store/store';
+
+const CurrencyRates: React.FC = () => {
+ const dispatch: AppDispatch = useDispatch();
+ const { rates, baseCurrency, status, error } = useSelector((state: RootState) => state.currency);
+ const [selectedCurrency, setSelectedCurrency] = useState(baseCurrency);
+
+ useEffect(() => {
+ if (status === 'idle') {
+ dispatch(fetchCurrencyRates(baseCurrency));
+ }
+ }, [status, baseCurrency, dispatch]);
+
+ const handleCurrencyChange = (event: React.ChangeEvent
) => {
+ const newCurrency = event.target.value;
+ setSelectedCurrency(newCurrency);
+ dispatch(setBaseCurrency(newCurrency));
+ dispatch(fetchCurrencyRates(newCurrency));
+ };
+
+ if (status === 'loading') {
+ return Загрузка...
;
+ }
+
+ if (status === 'failed') {
+ return Ошибка: {error}
;
+ }
+
+ return (
+
+
Курсы валют
+
+
+
+
+
+ {Object.entries(rates).map(([currency, rate]) => (
+ -
+ {currency}: {rate}
+
+ ))}
+
+
+ );
+};
+
+export default CurrencyRates;
\ No newline at end of file
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/counterSlice.ts b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/counterSlice.ts
index a4c03c204..7893e2ded 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/counterSlice.ts
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/counterSlice.ts
@@ -7,7 +7,7 @@ interface CounterState {
// initialState — это начальное состояние счетчика
const initialState: CounterState = {
- value: 0,
+ value: 1,
};
//Слайс — это часть хранилища, которая содержит редьюсер и действия
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/counterSliceAsync.ts b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/counterSliceAsync.ts
index 7b6b8423f..4db88b254 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/counterSliceAsync.ts
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/counterSliceAsync.ts
@@ -9,7 +9,7 @@ interface CounterStateAsync {
// Начальное состояние счетчика
const initialState: CounterStateAsync = {
- value: 0,
+ value: 2,
status: 'idle',
error: null,
};
@@ -34,7 +34,7 @@ export const fetchRandomNumber = createAsyncThunk(
// Создаем слайс (slice) для счетчика
const counterSliceAsync = createSlice({
- name: 'counter',
+ name: 'counterAsync',
initialState,
reducers: {
increment: (state) => {
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/currencySlice.ts b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/currencySlice.ts
new file mode 100644
index 000000000..aa7396f56
--- /dev/null
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/currencySlice.ts
@@ -0,0 +1,59 @@
+import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
+import axios from 'axios';
+
+interface CurrencyState {
+ rates: { [key: string]: number };
+ baseCurrency: string;
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+}
+
+const initialState: CurrencyState = {
+ rates: {},
+ baseCurrency: 'USD', // Начальная основная валюта
+ status: 'idle',
+ error: null,
+};
+
+// Асинхронный thunk для получения курсов валют
+export const fetchCurrencyRates = createAsyncThunk(
+ 'currency/fetchCurrencyRates',
+ async (baseCurrency: string) => {
+ const response = await axios.get(`https://api.exchangerate-api.com/v4/latest/${baseCurrency}`);
+ const data = response.data as { rates: { [key: string]: number } };
+ return { rates: data.rates, baseCurrency };
+ }
+);
+
+const currencySlice = createSlice({
+ name: 'currency',
+ initialState,
+ reducers: {
+ // редьюсер для изменения основной валюты
+ setBaseCurrency: (state, action: PayloadAction) => {
+ state.baseCurrency = action.payload;
+ },
+ },
+ // Добавляем обработчики для асинхронного thunk
+ extraReducers: (builder) => {
+ builder
+ // Обработчики для состояния загрузки
+ .addCase(fetchCurrencyRates.pending, (state) => {
+ state.status = 'loading';
+ })
+ // Обработчики для успешного получения данных
+ .addCase(fetchCurrencyRates.fulfilled, (state, action: PayloadAction<{ rates: { [key: string]: number }, baseCurrency: string }>) => {
+ state.status = 'succeeded';
+ state.rates = action.payload.rates;
+ state.baseCurrency = action.payload.baseCurrency;
+ })
+ // Обработчики для ошибки
+ .addCase(fetchCurrencyRates.rejected, (state, action) => {
+ state.status = 'failed';
+ state.error = action.error.message || 'Something went wrong';
+ });
+ },
+});
+
+export const { setBaseCurrency } = currencySlice.actions; // Экспортируем действие
+export default currencySlice.reducer;
\ No newline at end of file
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/store.ts b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/store.ts
index ceecd48fb..64ddda1f0 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/store.ts
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-redux-basic/src/store/store.ts
@@ -1,12 +1,16 @@
import { configureStore } from '@reduxjs/toolkit'; // Импортируем функцию configureStore из библиотеки @reduxjs/toolkit
-import counterReducerAsync from './counterSliceAsync'; // Импортируем редьюсер из слайса
+import counterReducer from './counterSlice'; // Импортируем редьюсер из слайса
+import counterReducerAsync from './counterSliceAsync';
+import currencyReducer from './currencySlice';
// configureStore — это функция, которая создает хранилище
// reducer — это объект, который содержит редьюсеры
// counterReducerAsync — это редьюсер счетчика
export const store = configureStore({
reducer: {
- counter: counterReducerAsync,
+ counter: counterReducer,
+ counterAsync: counterReducerAsync,
+ currency: currencyReducer,
},
});
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/BasicApp.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/BasicApp.tsx
index 70a4dd36e..e229d8006 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/BasicApp.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/BasicApp.tsx
@@ -1,24 +1,20 @@
import React from 'react';
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
import './App.css';
+import { NotFound } from './Components/NotFound';
-// Компонент для главной страницы
+// Компонент главной страницы
const Home: React.FC = () => {
return <>
-
Добро пожаловать на главную страницу!
+ Добро пожаловать!
>;
};
-// Компонент для страницы "О нас"
+// Компонент страницы "О нас"
const About: React.FC = () => {
- return О нас
;
-};
-
-// Компонент для страницы 404
-const NotFound: React.FC = () => {
- return Страница не найдена
;
+ return Тема занятия:
Изучаем React Router
;
};
// Основной компонент приложения
@@ -31,14 +27,14 @@ const BasicApp: React.FC = () => {
Главная
- О нас
+ Тема занятия
} />
- } />
+ } />
} /> {/* Страница 404 */}
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/NavigateApp.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/NavigateApp.tsx
index 312758ae7..c93885af7 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/NavigateApp.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/NavigateApp.tsx
@@ -1,26 +1,60 @@
-import React from 'react';
-import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
+import React, { useState } from 'react';
+import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate, Link } from 'react-router-dom';
-const PrivatePage: React.FC = () => {
- const isAuthenticated = false; // Пример проверки авторизации
+const Home: React.FC = () => {
+ return <>
+
+
Главная страница!
+ Приватная страница
+
+ >;
+};
+// Приватная страница с примером использования Navigate - перенаправление на страницу входа
+const PrivatePage: React.FC<{ isAuthenticated: boolean }> = ({ isAuthenticated }) => {
if (!isAuthenticated) {
- return ; // Перенаправление на страницу входа
+ return ; // Перенаправление на страницу входа если не авторизован
}
- return Приватная страница
;
+ return
+
Приватная страница
+ Главная страница
+ ;
};
-const Login: React.FC = () => {
- return Страница входа
;
+const Login: React.FC<{ onLogin: () => void }> = ({ onLogin }) => {
+ const navigate = useNavigate();
+
+ const handleLogin = () => {
+ onLogin();
+ navigate('/private');
+ };
+
+ return (
+
+
Авторизация
+
Введите свои учетные данные:
+
+
+
+
+
+ );
};
const NavigateApp: React.FC = () => {
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+
+ const handleLogin = () => {
+ setIsAuthenticated(true);
+ };
+
return (
- } />
- } />
+ } />
+ } />
+ } />
);
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/OutletApp.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/OutletApp.tsx
index de13a2bb5..5d635a991 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/OutletApp.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/OutletApp.tsx
@@ -6,7 +6,7 @@ import './App.css';
const Home: React.FC = () => {
return <>
-
Добро пожаловать на главную страницу!
+ Добро пожаловать!
>;
};
@@ -14,8 +14,8 @@ const Home: React.FC = () => {
//Компонент дашборда
const Dashboard: React.FC = () => {
return (
-
-
Дашборд
+
+
Компонент Dashboard
-
+
История заказов
+
+
+
+
+ | Дата |
+ Заказ |
+ Сумма |
+
+
+
+
+ | 01.01.2023 |
+ Заказ 1 |
+ $100 |
+
+
+ | 02.01.2023 |
+ Заказ 2 |
+ $200 |
+
+
+
{/* Outlet рендерит дочерние маршруты */}
- [][][]
- [][][]
);
};
const Profile: React.FC = () => {
- return
Профиль
;
+ return
Компонент Profile
+
+
Имя: Иван
+
Фамилия: Иванов
+
Возраст: 30
+
Страна: Россия
+
Город: Москва
+
;
};
const Settings: React.FC = () => {
- return <>
-
Настройки2
;
-
- >
+ return
+
Компонент Settings
+ Открыть опции
+
+
};
-const Settings3Level: React.FC = () => {
- return
Настройки3
;
+const Options: React.FC = () => {
+ return
Компонент Options
+
Закрыть
;
};
@@ -58,23 +87,22 @@ const OutletApp: React.FC = () => {
return (
+
+ -
+ Главная
+
+ -
+ Дашборд
+
+
+
} />
}>
} />
}>
- }/>
+ } />
-
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/RouteParamsApp.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/RouteParamsApp.tsx
index 7bac76d7f..17a4cbdab 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/RouteParamsApp.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/RouteParamsApp.tsx
@@ -1,11 +1,25 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useParams } from 'react-router-dom';
import './App.css';
+import { NotFound } from './Components/NotFound';
+
+// Компонент для страницы пользователя
+const Main: React.FC = () => {
+
+ return <>
+
+
Текст главной страницы с примером ссылки в тексте
+
+ >;
+};
// Компонент для страницы пользователя
const User: React.FC = () => {
- const { id } = useParams<{ id: string }>(); // Извлекаем параметр id из URL с помощью хука useParams
- return
Пользователь с ID: {id}
;
+ const { id, name } = useParams<{ id: string, name:string }>(); // Извлекаем параметр id из URL с помощью хука useParams
+ return
+
Пользователь с ID: {id}
+ Детали пользователя {id}
+ ;
};
const RouteParamsApp: React.FC = () => {
@@ -13,20 +27,25 @@ const RouteParamsApp: React.FC = () => {
- Страница Пользователь 1
- } />
+ } />
+ } />
+ } /> {/* Страница 404 */}
+
+
);
};
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/UseNavigateApp.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/UseNavigateApp.tsx
index b75c1dedd..6383f1acd 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/UseNavigateApp.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/UseNavigateApp.tsx
@@ -10,15 +10,30 @@ const Home: React.FC = () => {
};
return (
-
-
Главная страница
-
+
+
Главная страница
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Corporis asperiores soluta incidunt officia eum reprehenderit modi repudiandae pariatur quidem dolore?
+
+
);
};
const About: React.FC = () => {
- return
О нас
;
+ const navigate = useNavigate();
+ const goToHomePage = () => {
+ navigate('/'); // Переход на страницу "О нас"
+ };
+ return
+
Про useNavigate
+
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Fuga, ipsam sapiente omnis dicta mollitia quo id quaerat delectus culpa dolor!
+
+
+
;
};
const UseNavigateApp: React.FC = () => {
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/components/NotFound.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/components/NotFound.tsx
new file mode 100644
index 000000000..e99f56270
--- /dev/null
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/components/NotFound.tsx
@@ -0,0 +1,4 @@
+// Компонент для страницы 404
+export const NotFound: React.FC = () => {
+ return
Страница не найдена
;
+ };
\ No newline at end of file
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/index.css b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/index.css
index 6119ad9a8..11c2a482d 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/index.css
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/index.css
@@ -66,3 +66,14 @@ button:focus-visible {
background-color: #f9f9f9;
}
}
+
+.block {
+ display: block;
+ width: 100%;
+}
+
+.page {
+ max-width: 800px;
+ margin: 0 auto;
+ padding: 2em;
+}
\ No newline at end of file
diff --git a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/main.tsx b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/main.tsx
index 981b9145a..8e3a818d0 100644
--- a/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/main.tsx
+++ b/Lessons/Module 5. Frontend/3.react-basic-router-redux/react-router-basic/src/main.tsx
@@ -8,11 +8,11 @@ import OutletApp from './OutletApp.tsx'
import NavigateApp from './NavigateApp.tsx'
createRoot(document.getElementById('root')!).render(
-
- {/* */}
+ <>
+
{/* */}
{/* */}
+ {/* */}
{/* */}
-
- ,
+ >
)