epam-ai-conductor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/bin/workshop.js +6 -0
- package/dist/auth-check.d.ts +1 -0
- package/dist/auth-check.js +45 -0
- package/dist/auth-check.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +156 -0
- package/dist/cli.js.map +1 -0
- package/dist/content/content/module-1/task/module-1_home-task_components.md +257 -0
- package/dist/content/content/module-1/task/module-1_home-task_good_practices.md +69 -0
- package/dist/content/content/module-1/task/module-1_home-task_task.md +253 -0
- package/dist/content/content/module-1/tests/Button.test.tsx +22 -0
- package/dist/content/content/module-1/tests/CourseCard.test.tsx +64 -0
- package/dist/content/content/module-1/tests/CourseInfo.test.tsx +73 -0
- package/dist/content/content/module-1/tests/Courses.test.tsx +87 -0
- package/dist/content/content/module-1/tests/Header.test.tsx +23 -0
- package/dist/content/content/module-1/tests/Input.test.tsx +36 -0
- package/dist/content/content/module-1/tests/helpers.test.ts +13 -0
- package/dist/content/content/module-1/theory/module-1_elements-render.md +66 -0
- package/dist/content/content/module-1/theory/module-1_hooks_useEffect.md +178 -0
- package/dist/content/content/module-1/theory/module-1_hooks_useState.md +131 -0
- package/dist/content/content/module-1/theory/module-1_react.md +64 -0
- package/dist/content/content/module-1/theory/module-1_spa.md +74 -0
- package/dist/content/content/module-2/task/module-2_home-task_components.md +313 -0
- package/dist/content/content/module-2/task/module-2_home-task_good_practices.md +35 -0
- package/dist/content/content/module-2/task/module-2_home-task_task.md +210 -0
- package/dist/content/content/module-2/tests/App.test.tsx +54 -0
- package/dist/content/content/module-2/tests/Login.test.tsx +73 -0
- package/dist/content/content/module-2/tests/Registration.test.tsx +70 -0
- package/dist/content/content/module-2/tests/SearchBar.test.tsx +39 -0
- package/dist/content/content/module-2/theory/module-2_custom-hooks.md +201 -0
- package/dist/content/content/module-2/theory/module-2_hooks.md +117 -0
- package/dist/content/content/module-2/theory/module-2_react-router.md +328 -0
- package/dist/content/content/module-3/task/module-3_home-task_components.md +94 -0
- package/dist/content/content/module-3/task/module-3_home-task_good_practices.md +26 -0
- package/dist/content/content/module-3/task/module-3_home-task_task.md +170 -0
- package/dist/content/content/module-3/tests/App.test.tsx +54 -0
- package/dist/content/content/module-3/tests/Courses.test.tsx +87 -0
- package/dist/content/content/module-3/tests/CreateAuthor.test.tsx +44 -0
- package/dist/content/content/module-3/tests/CreateCourse.test.tsx +122 -0
- package/dist/content/content/module-3/theory/module-3_redux-hooks.md +194 -0
- package/dist/content/content/module-3/theory/module-3_state-actions-reducers.md +445 -0
- package/dist/content/content/module-4/task/module-4_home-task_components.md +187 -0
- package/dist/content/content/module-4/task/module-4_home-task_task.md +139 -0
- package/dist/content/content/module-4/tests/App.test.tsx +54 -0
- package/dist/content/content/module-4/tests/Courses.test.tsx +87 -0
- package/dist/content/content/module-4/tests/CreateCourse.test.tsx +122 -0
- package/dist/content/content/module-4/tests/Login.test.tsx +73 -0
- package/dist/content/content/module-4/theory/module-4_async-redux.md +99 -0
- package/dist/content/content/module-4/theory/module-4_private-routes.md +55 -0
- package/dist/content/content/module-5/task/module-5_home-task_instruction.md +68 -0
- package/dist/content/content/module-5/task/module-5_home-task_task.md +154 -0
- package/dist/content/content/module-5/tests/App.test.tsx +54 -0
- package/dist/content/content/module-5/tests/CourseCard.test.tsx +64 -0
- package/dist/content/content/module-5/tests/Courses.test.tsx +87 -0
- package/dist/content/content/module-5/tests/Header.test.tsx +23 -0
- package/dist/content/content/module-5/theory/module-5_react-testing-library_example.md +379 -0
- package/dist/content/content/module-5/theory/module-5_redux-writing-tests.md +246 -0
- package/dist/content/module-1/task/module-1_home-task_components.md +257 -0
- package/dist/content/module-1/task/module-1_home-task_good_practices.md +69 -0
- package/dist/content/module-1/task/module-1_home-task_task.md +253 -0
- package/dist/content/module-1/tests/Button.test.tsx +22 -0
- package/dist/content/module-1/tests/CourseCard.test.tsx +64 -0
- package/dist/content/module-1/tests/CourseInfo.test.tsx +73 -0
- package/dist/content/module-1/tests/Courses.test.tsx +87 -0
- package/dist/content/module-1/tests/Header.test.tsx +23 -0
- package/dist/content/module-1/tests/Input.test.tsx +36 -0
- package/dist/content/module-1/tests/helpers.test.ts +13 -0
- package/dist/content/module-1/theory/module-1_elements-render.md +66 -0
- package/dist/content/module-1/theory/module-1_hooks_useEffect.md +178 -0
- package/dist/content/module-1/theory/module-1_hooks_useState.md +131 -0
- package/dist/content/module-1/theory/module-1_react.md +64 -0
- package/dist/content/module-1/theory/module-1_spa.md +74 -0
- package/dist/content/module-2/task/module-2_home-task_components.md +313 -0
- package/dist/content/module-2/task/module-2_home-task_good_practices.md +35 -0
- package/dist/content/module-2/task/module-2_home-task_task.md +210 -0
- package/dist/content/module-2/tests/App.test.tsx +54 -0
- package/dist/content/module-2/tests/Login.test.tsx +73 -0
- package/dist/content/module-2/tests/Registration.test.tsx +70 -0
- package/dist/content/module-2/tests/SearchBar.test.tsx +39 -0
- package/dist/content/module-2/theory/module-2_custom-hooks.md +201 -0
- package/dist/content/module-2/theory/module-2_hooks.md +117 -0
- package/dist/content/module-2/theory/module-2_react-router.md +328 -0
- package/dist/content/module-3/task/module-3_home-task_components.md +94 -0
- package/dist/content/module-3/task/module-3_home-task_good_practices.md +26 -0
- package/dist/content/module-3/task/module-3_home-task_task.md +170 -0
- package/dist/content/module-3/tests/App.test.tsx +54 -0
- package/dist/content/module-3/tests/Courses.test.tsx +87 -0
- package/dist/content/module-3/tests/CreateAuthor.test.tsx +44 -0
- package/dist/content/module-3/tests/CreateCourse.test.tsx +122 -0
- package/dist/content/module-3/theory/module-3_redux-hooks.md +194 -0
- package/dist/content/module-3/theory/module-3_state-actions-reducers.md +445 -0
- package/dist/content/module-4/task/module-4_home-task_components.md +187 -0
- package/dist/content/module-4/task/module-4_home-task_task.md +139 -0
- package/dist/content/module-4/tests/App.test.tsx +54 -0
- package/dist/content/module-4/tests/Courses.test.tsx +87 -0
- package/dist/content/module-4/tests/CreateCourse.test.tsx +122 -0
- package/dist/content/module-4/tests/Login.test.tsx +73 -0
- package/dist/content/module-4/theory/module-4_async-redux.md +99 -0
- package/dist/content/module-4/theory/module-4_private-routes.md +55 -0
- package/dist/content/module-5/task/module-5_home-task_instruction.md +68 -0
- package/dist/content/module-5/task/module-5_home-task_task.md +154 -0
- package/dist/content/module-5/tests/App.test.tsx +54 -0
- package/dist/content/module-5/tests/CourseCard.test.tsx +64 -0
- package/dist/content/module-5/tests/Courses.test.tsx +87 -0
- package/dist/content/module-5/tests/Header.test.tsx +23 -0
- package/dist/content/module-5/theory/module-5_react-testing-library_example.md +379 -0
- package/dist/content/module-5/theory/module-5_redux-writing-tests.md +246 -0
- package/dist/content-loader.d.ts +7 -0
- package/dist/content-loader.js +26 -0
- package/dist/content-loader.js.map +1 -0
- package/dist/context-builder.d.ts +2 -0
- package/dist/context-builder.js +116 -0
- package/dist/context-builder.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
sidebar_label: 'What is React Router?'
|
|
4
|
+
title: 'A Simple React Router'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What is React Router?
|
|
8
|
+
|
|
9
|
+
React Router is a library for routing in React. It helps users to navigate among views of
|
|
10
|
+
various components in a React Application and allows changing the browser URL. Basically it
|
|
11
|
+
means that user is able to see "different pages" of SPA changing the URL. The main feature of
|
|
12
|
+
React Router is that there is no need to make request to server again, everything happens on
|
|
13
|
+
the client side without page refreshing.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Run this command to install the React Router:
|
|
18
|
+
|
|
19
|
+
```http request
|
|
20
|
+
npm install react-router-dom
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This command will add React Router to dependencies of your project.
|
|
24
|
+
|
|
25
|
+
:::tip
|
|
26
|
+
To install a certain version of `react-router-dom` you can add "@[version]" to the end.
|
|
27
|
+
For example, to install the 6th version run:
|
|
28
|
+
`npm install react-router-dom@6`
|
|
29
|
+
:::
|
|
30
|
+
|
|
31
|
+
## Connecting React Router to App
|
|
32
|
+
|
|
33
|
+
When we are done with installation we should connect our app to the browser's URL:
|
|
34
|
+
import
|
|
35
|
+
`BrowserRouter` and wrap `App` component in it.
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// src/index.js
|
|
39
|
+
import ReactDOM from 'react-dom';
|
|
40
|
+
import App from './App';
|
|
41
|
+
|
|
42
|
+
// add this import
|
|
43
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
44
|
+
|
|
45
|
+
const root = ReactDOM.createRoot(
|
|
46
|
+
document.getElementById('root')
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
root.render(
|
|
50
|
+
<BrowserRouter>
|
|
51
|
+
<App />
|
|
52
|
+
</BrowserRouter>
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Now let's add some links to our application.
|
|
57
|
+
|
|
58
|
+
## Adding Links
|
|
59
|
+
|
|
60
|
+
As you already know, we are not going to refresh our page when we navigate through pages
|
|
61
|
+
(components) inside our app, that's why we shouldn't use regular tag:
|
|
62
|
+
```html
|
|
63
|
+
<a href="URL">...</a>
|
|
64
|
+
```
|
|
65
|
+
instead, we should use special component:
|
|
66
|
+
```jsx
|
|
67
|
+
<Link to="URL">...</Link>
|
|
68
|
+
```
|
|
69
|
+
which is provided by `react-router-dom` library, so we can just import it and use, for
|
|
70
|
+
instance, inside our App component.
|
|
71
|
+
|
|
72
|
+
```jsx
|
|
73
|
+
// src/component/Login/Login.jsx.jsx
|
|
74
|
+
|
|
75
|
+
import { Link } from "react-router-dom";
|
|
76
|
+
|
|
77
|
+
const Login = () => {
|
|
78
|
+
return (
|
|
79
|
+
<div>
|
|
80
|
+
<form>
|
|
81
|
+
// Login form code
|
|
82
|
+
</form>
|
|
83
|
+
<div>
|
|
84
|
+
<Link to="/regiscration">Registration</Link>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If you click on the link 'Registration' you can see that URL changes and the page does not reload when
|
|
92
|
+
we navigate to the `Registration` component. So now, when we have links, it is time to create our routes
|
|
93
|
+
with components which will be rendered when we follow our links.
|
|
94
|
+
|
|
95
|
+
## Adding Routes
|
|
96
|
+
|
|
97
|
+
First, let's create two components for **Login** and **Registration** page.
|
|
98
|
+
|
|
99
|
+
* src/components/Login.jsx
|
|
100
|
+
* src/component/Registration.jsx
|
|
101
|
+
|
|
102
|
+
Inside both pages let's add some `jsx` in order to see some difference between them:
|
|
103
|
+
```jsx
|
|
104
|
+
// src/pages/FirstPage.jsx
|
|
105
|
+
|
|
106
|
+
import { Link } from "react-router-dom";
|
|
107
|
+
|
|
108
|
+
const Login = () => {
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
<h2>Login</h2>
|
|
112
|
+
<form>
|
|
113
|
+
// Login form code
|
|
114
|
+
</form>
|
|
115
|
+
<p>If you don't have an account you may</p>
|
|
116
|
+
<Link to="/registration">Registration</Link>
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```jsx
|
|
123
|
+
// src/pages/SecondPage.jsx
|
|
124
|
+
|
|
125
|
+
import { Link } from "react-router-dom";
|
|
126
|
+
|
|
127
|
+
const Registration = () => {
|
|
128
|
+
return (
|
|
129
|
+
<>
|
|
130
|
+
<h2>Registration</h2>
|
|
131
|
+
<form>
|
|
132
|
+
// Registration form code
|
|
133
|
+
</form>
|
|
134
|
+
<p>If you have an account you may </p>
|
|
135
|
+
<Link to="/registration">Login</Link>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Now we can add our components as Routes of our application.
|
|
142
|
+
For this purpose we should import `Routes` and `Route` components from "react-router-dom" library.
|
|
143
|
+
|
|
144
|
+
```jsx
|
|
145
|
+
// src/App.js
|
|
146
|
+
|
|
147
|
+
import { Routes, Route } from "react-router-dom";
|
|
148
|
+
|
|
149
|
+
import { Courses } from "./components/Courses.jsx";
|
|
150
|
+
import { Login } from "./component/Login.jsx";
|
|
151
|
+
import { Registration } from "./component/Registration.jsx";
|
|
152
|
+
|
|
153
|
+
const App = () => {
|
|
154
|
+
return (
|
|
155
|
+
<Routes>
|
|
156
|
+
<Route path="/" element={<Courses />}/>
|
|
157
|
+
<Route path="/login" element={<Login />}/>
|
|
158
|
+
<Route path="/registration" element={<Registration />}/>
|
|
159
|
+
</Routes>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Pay attention that **Route** components should be inside **Routes** component.
|
|
165
|
+
For the `element` attribute of **Route** we have to provide a component
|
|
166
|
+
we want to be rendered when our relative path matches with `path` attribute.
|
|
167
|
+
|
|
168
|
+
### Previous versions of `react-router-dom`
|
|
169
|
+
|
|
170
|
+
Sometimes, on the "old" projects, you may see another version of routing. In our case it
|
|
171
|
+
would look like this:
|
|
172
|
+
|
|
173
|
+
```jsx
|
|
174
|
+
// src/App.js
|
|
175
|
+
|
|
176
|
+
import { Route, Switch } from "react-router-dom";
|
|
177
|
+
|
|
178
|
+
import { Courses } from "./components/Courses.jsx";
|
|
179
|
+
import { Login } from "./component/Login.jsx";
|
|
180
|
+
import { Registration } from "./component/Registration.jsx";
|
|
181
|
+
|
|
182
|
+
render(
|
|
183
|
+
<Switch>
|
|
184
|
+
<Route exact path="/" children={<Courses />} />
|
|
185
|
+
<Route path="/login" children={<Login />} />
|
|
186
|
+
<Route path="/registration" children={<Registration />} />
|
|
187
|
+
</Switch>
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This is the older version of `react-router-dom`.
|
|
192
|
+
**Pay attention that this code will not work with `react-router-dom` v6. **So if you want
|
|
193
|
+
to try this, don't forget to install `react-router-dom` version 5.
|
|
194
|
+
You can find additional information here:
|
|
195
|
+
[Simple React Router v4 Tutorial](https://blog.pshrmn.com/simple-react-router-v4-tutorial/)
|
|
196
|
+
|
|
197
|
+
:::info
|
|
198
|
+
You may also create routes by using the [useRoutes()](./hooks.md#useroutes) hook, which we'll cover in the [React Router Hooks](./hooks.md) section.
|
|
199
|
+
:::
|
|
200
|
+
|
|
201
|
+
## Path Params
|
|
202
|
+
|
|
203
|
+
Sometimes you need to get information from the URL to render a specific page.
|
|
204
|
+
You can retract the information by using
|
|
205
|
+
`path params`, and pass that information from the URL to the page.
|
|
206
|
+
|
|
207
|
+
To see how it works let's do the following steps:
|
|
208
|
+
|
|
209
|
+
1. Create new component. In our example it will be `CourseInfo`.
|
|
210
|
+
|
|
211
|
+
```jsx
|
|
212
|
+
// src/component/CourseInfo.jsx
|
|
213
|
+
|
|
214
|
+
const CourseInfo = () => {
|
|
215
|
+
return <h2>Course id that came from URL is ???</h2>;
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
And we want to show some course id on the page instead of `???`.
|
|
219
|
+
|
|
220
|
+
2. Now we have to add course item inside `Courses` component.
|
|
221
|
+
```jsx
|
|
222
|
+
// src/component/Courses.jsx
|
|
223
|
+
|
|
224
|
+
import { Link } from "react-router-dom";
|
|
225
|
+
|
|
226
|
+
const Courses = () => {
|
|
227
|
+
return (
|
|
228
|
+
<>
|
|
229
|
+
<p>React course</p>
|
|
230
|
+
<Link to={`/courses/${id}`}>SHOW COURSE</Link>
|
|
231
|
+
</>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
You can see that currently we don't have pages for routes `/courses/:courseId`. For this purposes we
|
|
236
|
+
should do this step next.
|
|
237
|
+
|
|
238
|
+
3. Creating new `Route` with URL param `:courseId`.
|
|
239
|
+
|
|
240
|
+
```jsx
|
|
241
|
+
// src/App.js
|
|
242
|
+
|
|
243
|
+
import { Routes, Route } from "react-router-dom";
|
|
244
|
+
|
|
245
|
+
import { Courses } from "./components/Courses.jsx";
|
|
246
|
+
import { Login } from "./component/Login.jsx";
|
|
247
|
+
import { Registration } from "./component/Registration.jsx";
|
|
248
|
+
import { CourseInfo } from "./compoments/CourseInfo.jsx";
|
|
249
|
+
|
|
250
|
+
render(
|
|
251
|
+
<Switch>
|
|
252
|
+
<Route exact path="/" children={<Courses />} />
|
|
253
|
+
<Route path="/login" children={<Login />} />
|
|
254
|
+
<Route path="/registration" children={<Registration />} />
|
|
255
|
+
<Route path='/courses/:courseId' element={<CourseInfo/>}/>
|
|
256
|
+
</Switch>
|
|
257
|
+
);
|
|
258
|
+
```
|
|
259
|
+
This `:courseId` construction means that this URL will match any URL like `courses/id1`, or
|
|
260
|
+
`courses/id2`.
|
|
261
|
+
|
|
262
|
+
5. And the last step is passing this param to our `CourseInfo`. For this purpose we should import
|
|
263
|
+
`useParams` hook from `react-router-dom` and use it like this:
|
|
264
|
+
|
|
265
|
+
```jsx
|
|
266
|
+
// src/component/CourseInfo.jsx
|
|
267
|
+
|
|
268
|
+
import { useParams } from "react-router-dom";
|
|
269
|
+
|
|
270
|
+
const CourseInfo = () => {
|
|
271
|
+
const params = useParams();
|
|
272
|
+
|
|
273
|
+
return <h2>Course id that came from URL is {params.courseId}</h2>;
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
:::caution
|
|
277
|
+
**courseId** should be the same key as we're passing `courseId` in our route in the `App` component.
|
|
278
|
+
:::
|
|
279
|
+
|
|
280
|
+
And if we click on `SHOW COURSE`, we can see page with `CourseInfo` component and selected id.
|
|
281
|
+
|
|
282
|
+
## Redirect
|
|
283
|
+
|
|
284
|
+
Also, there is a possibility to redirect user from some, for instance, non-existing URL. To do that
|
|
285
|
+
we should create such construction.
|
|
286
|
+
```jsx
|
|
287
|
+
<Route
|
|
288
|
+
path="*"
|
|
289
|
+
element={<Navigate to="/" />}
|
|
290
|
+
/>
|
|
291
|
+
```
|
|
292
|
+
:::caution
|
|
293
|
+
Don't forget to import `Navigate` component from `react-router-dom`.
|
|
294
|
+
:::
|
|
295
|
+
|
|
296
|
+
This will redirect user from all non-existing URLs to the one stated in the element attribute.
|
|
297
|
+
`*` element means that if all routes above didn't match, this path will be used for the address.
|
|
298
|
+
So if we use this construction in our application, all our attempts to visit for instance URL `localhost:3000/hello` will navigate us back to
|
|
299
|
+
`localhost:3000/`:
|
|
300
|
+
|
|
301
|
+
```jsx
|
|
302
|
+
// src/App.jsx
|
|
303
|
+
|
|
304
|
+
import { Routes, Route, Navigate } from "react-router-dom";
|
|
305
|
+
|
|
306
|
+
import { Courses } from "./components/Courses.jsx";
|
|
307
|
+
import { Login } from "./component/Login.jsx";
|
|
308
|
+
import { Registration } from "./component/Registration.jsx";
|
|
309
|
+
import { CourseInfo } from "./compoments/CourseInfo.jsx";
|
|
310
|
+
|
|
311
|
+
render(
|
|
312
|
+
<Switch>
|
|
313
|
+
<Route exact path="/" children={<Courses />} />
|
|
314
|
+
<Route path="/login" children={<Login />} />
|
|
315
|
+
<Route path="/registration" children={<Registration />} />
|
|
316
|
+
<Route path='/courses/:courseId' element={<CourseInfo/>}/>
|
|
317
|
+
<Route path="*" element={<Navigate to="/" />}/>
|
|
318
|
+
</Switch>
|
|
319
|
+
);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Conclusion
|
|
323
|
+
That's it. Congratulations! We have covered all basic topics and successfully implemented navigation
|
|
324
|
+
in our simple React application using `React Router`.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
## Materials
|
|
328
|
+
1. [Official React Router documentation. Tutorial](https://reactrouter.com/en/6.18.0/start/tutorial)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
sidebar_label: "COMPONENTS"
|
|
4
|
+
title: Components description for Tasks 3
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## App component
|
|
8
|
+
|
|
9
|
+
1. Use [configureStore](https://redux-toolkit.js.org/api/configureStore) function to create store.
|
|
10
|
+
2. Import store to the `index.js`.
|
|
11
|
+
3. Wrap `App` component in the [Provider](https://react-redux.js.org/tutorials/quick-start#provide-the-redux-store-to-react).
|
|
12
|
+
4. Pass `store` to the `Provider`.
|
|
13
|
+
|
|
14
|
+
Now all components have access to the store, and you can use `Redux Hooks`.
|
|
15
|
+
|
|
16
|
+
:::tip
|
|
17
|
+
[Reading State from the Store with `useSelector`](https://redux.js.org/tutorials/fundamentals/part-5-ui-react#reading-state-from-the-store-with-useselector)
|
|
18
|
+
[Dispatch actions using `useDispatch`](https://react-redux.js.org/api/hooks#usedispatch)
|
|
19
|
+
:::
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Courses component
|
|
24
|
+
|
|
25
|
+
### Implement new feature for Courses component:
|
|
26
|
+
|
|
27
|
+
- Get courses data from the back-end. See SWAGGER `/courses/all` API.
|
|
28
|
+
|
|
29
|
+
- Get authors data from the back-end. See SWAGGER `/authors/all` API.
|
|
30
|
+
|
|
31
|
+
- Save courses and authors responses' results to the store.
|
|
32
|
+
|
|
33
|
+
- Courses list should be rendered based on the data from the `courses` and `authors` properties in the **store**.
|
|
34
|
+
|
|
35
|
+
:::tip
|
|
36
|
+
|
|
37
|
+
See example [How to create your first reducer](/docs/module-3/state-actions-reducers)
|
|
38
|
+
:::
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## CourseCard component
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
[Figma link](https://www.figma.com/design/9MsU97rn21GuQIT01xwLuc/React-Fundamentals-Course?node-id=2905-67147&t=2XuYekWUy7fL4Ba9-0)
|
|
46
|
+
|
|
47
|
+
### Implement new features for CourseCard component:
|
|
48
|
+
|
|
49
|
+
- Update `CourseCard` component by adding two buttons: `Delete` and `Update`.
|
|
50
|
+
(These buttons should be reusable component `Button`).
|
|
51
|
+
|
|
52
|
+
- When user clicks the `Delete` button current course should be removed from the
|
|
53
|
+
store and new courses list should be rendered.
|
|
54
|
+
|
|
55
|
+
- `Update` button has **NO functionality in this module**.
|
|
56
|
+
It will be added in the next module.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Add new course
|
|
61
|
+
|
|
62
|
+
### Implement new features for CreateCourse component:
|
|
63
|
+
|
|
64
|
+
- When user clicks `Create author` button the new author should be saved to the store.
|
|
65
|
+
|
|
66
|
+
- When user clicks `Create course` button the new course should be saved to the store.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Login component
|
|
71
|
+
|
|
72
|
+
### Implement new features for Login component:
|
|
73
|
+
|
|
74
|
+
- Save information (name, token, email) from response to the store.
|
|
75
|
+
|
|
76
|
+
- Save token to the `localStorage` after success login.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
:::caution
|
|
81
|
+
If we have a token in the `localStorage` the user should be immediately redirected to the Courses page when opening the application.
|
|
82
|
+
:::
|
|
83
|
+
|
|
84
|
+
## Header
|
|
85
|
+
|
|
86
|
+
### Implement new features for Header component:
|
|
87
|
+
|
|
88
|
+
- Take information about user from the store.
|
|
89
|
+
|
|
90
|
+
- When user clicks `LOGOUT` button you should dispatch action to the store.
|
|
91
|
+
|
|
92
|
+
- Delete token from the `localStorage` after logout.
|
|
93
|
+
|
|
94
|
+
---
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
sidebar_label: 'GOOD PRACTICES'
|
|
4
|
+
title: GOOD PRACTICES
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GOOD PRACTICES THAT YOU CAN APPLY FOR THIS TASK:
|
|
8
|
+
1. Use `Redux DevTools` extension for Chrome browser to see store changes in the browser.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
2. Use separate `selectors.js` file for `useSelector` callbacks. For example:
|
|
12
|
+
```js
|
|
13
|
+
// BAD
|
|
14
|
+
|
|
15
|
+
// Courses.jsx
|
|
16
|
+
const courses = useSelector(state => state.courses);
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// GOOD
|
|
20
|
+
|
|
21
|
+
// selectors.js
|
|
22
|
+
export const getCourses = state => state.courses;
|
|
23
|
+
|
|
24
|
+
// Courses.jsx
|
|
25
|
+
const courses = useSelector(getCourses);
|
|
26
|
+
```
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
sidebar_label: 'TASK'
|
|
4
|
+
title: 'Module 3. State Management'
|
|
5
|
+
---
|
|
6
|
+
This is the third part of the application.
|
|
7
|
+
In this assignment,
|
|
8
|
+
you should continue to work on your project with a slight modification.
|
|
9
|
+
This module is designed to teach how to work with state management in React and what is Redux.
|
|
10
|
+
|
|
11
|
+
## Prerequisite:
|
|
12
|
+
Firstly, you need to install the [Redux Toolkit](https://redux-toolkit.js.org/introduction/getting-started) and React-redux modules in your project using npm:
|
|
13
|
+
|
|
14
|
+
1. Go to your project directory
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
2. Install redux and react-redux
|
|
18
|
+
```http request
|
|
19
|
+
npm i @reduxjs/toolkit react-redux --save
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
3. Run your project
|
|
23
|
+
```http request
|
|
24
|
+
npm run start
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Project structure requirements:
|
|
28
|
+
|
|
29
|
+
1. Create new folders where put the code for redux `src/store`.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
2. Create new file `services.js / .ts`.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
3. Folder structure `src/store`:
|
|
36
|
+
```
|
|
37
|
+
src
|
|
38
|
+
|-- store
|
|
39
|
+
| |-- index.js/.ts // Add store creation
|
|
40
|
+
| |
|
|
41
|
+
| |-- rootReducer.js/.ts // combine your reducers here
|
|
42
|
+
| |
|
|
43
|
+
| |
|
|
44
|
+
| |-- user
|
|
45
|
+
| | |-- reducer.js // Code with reducer for user
|
|
46
|
+
| | |-- actions.js // Code with actions
|
|
47
|
+
| | |__ types.js // Code with action types
|
|
48
|
+
| |
|
|
49
|
+
| |-- courses
|
|
50
|
+
| | |-- reducer.js // Code with reducer for courses
|
|
51
|
+
| | |-- actions.js // Code with actions
|
|
52
|
+
| | |__ types.js // Code with action types
|
|
53
|
+
| |
|
|
54
|
+
| |__ authors
|
|
55
|
+
| |-- reducer.js // Code with reducer for user
|
|
56
|
+
| |-- actions.js // Code with actions
|
|
57
|
+
| |__ types.js // Code with action types
|
|
58
|
+
|
|
|
59
|
+
|-- services.js
|
|
60
|
+
|
|
|
61
|
+
|__ ...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
4. Delete `mockedCoursesList` and `mockedAuthorsList`.
|
|
65
|
+
|
|
66
|
+
5. Store should have model where `user`, `authors` and `courses` should have own reducer:
|
|
67
|
+
```javascript
|
|
68
|
+
const store = {
|
|
69
|
+
user: {
|
|
70
|
+
isAuth: boolean, // default value - false. After success login - true
|
|
71
|
+
name: string, // default value - empty string. After success login - name of user
|
|
72
|
+
email: string, // default value - empty string. After success login - email of user
|
|
73
|
+
token: string, // default value - empty string or token value from localStorage. After success login - value from API /login response. See Swagger.
|
|
74
|
+
},
|
|
75
|
+
courses: [], // list of courses. Default value - empty array. After success getting courses - value from API /courses/all response. See Swagger.
|
|
76
|
+
authors: [] // list of authors. Default value - empty array. After success getting authors - value from API /authors/all response. See Swagger.
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
6. APIs from SWAGGER for Module 3:
|
|
81
|
+
- `courses/all`[GET];
|
|
82
|
+
- `authors/all`[GET].
|
|
83
|
+
|
|
84
|
+
## Criteria (35 points max)
|
|
85
|
+
|
|
86
|
+
### APIs
|
|
87
|
+
|
|
88
|
+
* [2 points] - All functionality with back-end should be in `services.js`.
|
|
89
|
+
(Create **api service** for each endpoint that you use and call methods or functions from service into your components)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
* [2 points] - Get courses from back-end. See API `/courses/all` in the SWAGGER. Save courses list to the store.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
* [2 points] - Get authors from back-end. See API `/authors/all` in the SWAGGER. Save authors list to the store.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
### Store
|
|
99
|
+
* [3 points] - Store should have **User** reducer.
|
|
100
|
+
**User** reducer manage data with user's info and should have next model:
|
|
101
|
+
```js
|
|
102
|
+
const userInitialState = {
|
|
103
|
+
isAuth: boolean, // default value - false. After success login - true
|
|
104
|
+
name: string, // default value - empty string. After success login - name of user
|
|
105
|
+
email: string, // default value - empty string. After success login - email of user
|
|
106
|
+
token: string, // default value - empty string or token value from localStorage.
|
|
107
|
+
// After success login - value from API /login response. See Swagger.
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
**User** reducer has logic:
|
|
111
|
+
- After success login `isAuth` property has value `true`, save token, email and name.
|
|
112
|
+
- After logout `isAuth` property has value `false`, token, email and name have value as empty string.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
* [3 point] - Store should have **Courses** reducer.
|
|
116
|
+
**Courses** reducer manage data with courses list and should have next model:
|
|
117
|
+
```js
|
|
118
|
+
const coursesInitialState = [] // default value - empty array. After success getting courses from API - array of courses.
|
|
119
|
+
```
|
|
120
|
+
**Courses** reducer has logic:
|
|
121
|
+
- Get courses. Save courses to the **store** after getting them from API. See Swagger `/courses/all`.
|
|
122
|
+
- Save new course.
|
|
123
|
+
- Delete course.
|
|
124
|
+
- Update course (we will use this action in the next task).
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
* [3 point] - Store should have **Authors** reducer.
|
|
128
|
+
**Authors** reducer manage data with authors list and should have the next model:
|
|
129
|
+
```js
|
|
130
|
+
const authorsInitialState = [] // default value - empty array. After success getting authors from API - array of authors.
|
|
131
|
+
```
|
|
132
|
+
**Authors** reducer has logic:
|
|
133
|
+
- Save a new author.
|
|
134
|
+
- Get authors. Save authors to **store** after getting them from API. See Swagger `/authors/all` .
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
### Login Component
|
|
138
|
+
* [2 point] - Save user's info to the store after success login.
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
### Header Component
|
|
142
|
+
* [1 point] - Get user's name from the store.
|
|
143
|
+
|
|
144
|
+
* [2 point] - Dispatch REMOVE_USER action by `LOGOUT` button click.
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
### Courses Component
|
|
148
|
+
* [3 point] - Get courses from the store and render them into **Courses** component.
|
|
149
|
+
|
|
150
|
+
* [3 points] - If token is present in the localStorage the user should be immediately redirected to the `Courses` page when opening the application.
|
|
151
|
+
|
|
152
|
+
### CreateCourse Component
|
|
153
|
+
* [2 points] - After saving course in **CreateCourse** component course should be added to **store**.
|
|
154
|
+
|
|
155
|
+
* [2 point] - Get authors from the store.
|
|
156
|
+
|
|
157
|
+
* [1 point] - Save new authors to the store.
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
### CourseCard Component
|
|
161
|
+
* [2 point] - Add a new button "Delete course" into CourseCard.
|
|
162
|
+
|
|
163
|
+
* [1 point] - Add a new button "Update course" into CourseCard *(**CourseCard** in COMPONENTS)*.
|
|
164
|
+
|
|
165
|
+
* [1 point] - After clicking on the `Delete course` button a selected course should be deleted from the **store**.
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
:::info
|
|
169
|
+
Please find detailed description of components and functionality in the [COMPONENTS](components) section.
|
|
170
|
+
:::
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { MemoryRouter } from "react-router-dom";
|
|
4
|
+
import App from "../App";
|
|
5
|
+
|
|
6
|
+
describe("App", () => {
|
|
7
|
+
test("renders Login component when token is not present", () => {
|
|
8
|
+
localStorage.removeItem("token");
|
|
9
|
+
render(
|
|
10
|
+
<MemoryRouter initialEntries={["/"]}>
|
|
11
|
+
<App />
|
|
12
|
+
</MemoryRouter>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(
|
|
16
|
+
screen.getByText(/If you don't have an account you can/)
|
|
17
|
+
).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("renders Courses component when token is present", () => {
|
|
21
|
+
localStorage.setItem("token", "token");
|
|
22
|
+
render(
|
|
23
|
+
<MemoryRouter initialEntries={["/"]}>
|
|
24
|
+
<App />
|
|
25
|
+
</MemoryRouter>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const courseElements = screen.getAllByTestId("courseCard");
|
|
29
|
+
|
|
30
|
+
expect(courseElements[0]).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("renders Registration component", () => {
|
|
34
|
+
render(
|
|
35
|
+
<MemoryRouter initialEntries={["/registration"]}>
|
|
36
|
+
<App />
|
|
37
|
+
</MemoryRouter>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(screen.getByLabelText("name")).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByLabelText("email")).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByLabelText("password")).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("renders course add component", () => {
|
|
46
|
+
render(
|
|
47
|
+
<MemoryRouter initialEntries={["/courses/add"]}>
|
|
48
|
+
<App />
|
|
49
|
+
</MemoryRouter>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(screen.getByTestId("titleInput")).toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
});
|