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,445 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
sidebar_label: "State, Actions and Reducers"
|
|
4
|
+
title: "State, Actions and Reducers"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The next step to manage a work with the store is to create `Reducer` with `State`.
|
|
8
|
+
|
|
9
|
+
The easiest way to understand how it works is to consider it as an example.
|
|
10
|
+
Let's create a store for our 'Courses' application.
|
|
11
|
+
|
|
12
|
+
**Files structure**:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
store
|
|
16
|
+
|-- courses
|
|
17
|
+
| |-- reducer.js
|
|
18
|
+
| |-- actions.js
|
|
19
|
+
| |__ types.js
|
|
20
|
+
|
|
|
21
|
+
|-- rootReducer.js
|
|
22
|
+
|
|
|
23
|
+
|-- index.js
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Initial state
|
|
27
|
+
|
|
28
|
+
One of the core principles of React and Redux is that **your UI should be based on your state**.
|
|
29
|
+
One strategy for designing an application involves initially considering all the state required to describe its functionality.
|
|
30
|
+
It is also beneficial to aim for describing your UI using as few values in the state as possible,
|
|
31
|
+
which results in less data to monitor and update.
|
|
32
|
+
|
|
33
|
+
\*Conceptually, the primary aspect of this application is the actual list of current course items.
|
|
34
|
+
|
|
35
|
+
In our case, since we don't have any courses for the initial state, we should obtain them through an API request.
|
|
36
|
+
|
|
37
|
+
```jsx
|
|
38
|
+
const initialCoursesState = [];
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Actions
|
|
42
|
+
|
|
43
|
+
**Actions are plain JavaScript objects that have a `type` field.**
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const ACTION = {type: string, payload?: any};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
_Similar to designing the state structure based on the app's requirements, we should be able to generate a list of
|
|
50
|
+
actions that describe the events occurring in the app:_
|
|
51
|
+
|
|
52
|
+
1. _Save courses from the response to the store_
|
|
53
|
+
2. _Add a new course entry based on the data the user entered._
|
|
54
|
+
3. _Delete a course._
|
|
55
|
+
|
|
56
|
+
Typically, any additional data needed to describe an event is placed in the `action.payload` field. This data could be a number, a string, or an object with multiple fields.
|
|
57
|
+
|
|
58
|
+
`action.type` is simply a string, and you can create any type you prefer. The Redux store is not concerned with the actual text of the `action.type` field. It's recommended to select action types that are easily understandable and accurately describe the event.
|
|
59
|
+
This way, it will be much more straightforward to realize the actions when reviewing them later.
|
|
60
|
+
|
|
61
|
+
_For our COURSES application, let's create the action types in a `types.js` file._
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
//types.js
|
|
65
|
+
|
|
66
|
+
export const SAVE_COURSES = "SAVE_COURSES";
|
|
67
|
+
export const DELETE_COURSE = "DELETE_COURSE";
|
|
68
|
+
export const ADD_COURSE = "ADD_COURSE";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
_The next step is to create action creators for these types in an `actions.js` file._
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
// actions.js
|
|
75
|
+
|
|
76
|
+
import { SAVE_COURSES, DELETE_COURSE, ADD_COURSE } from './types.js'
|
|
77
|
+
|
|
78
|
+
const addCourseAction = (payload) => ({ type: ADD_COURSE, payload });
|
|
79
|
+
const deleteCourseAction = (payload) => ({ type: DELETE_COURSE, payload });
|
|
80
|
+
const saveCoursesAction = (payload) => ({ type: SAVE_COURSES, payload });
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Reducers
|
|
85
|
+
|
|
86
|
+
With the knowledge of our state structure and the actions, we can now proceed to write our first reducer.
|
|
87
|
+
|
|
88
|
+
**Reducers are functions that take the current `state` and an `action` as arguments, and return a new `state` result.**
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
const reducer = (state, action) => newState;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Creating the Reducer
|
|
95
|
+
|
|
96
|
+
A Redux app essentially has a single reducer function: the "root reducer" function that
|
|
97
|
+
will be passed to `configureStore` at a later stage.<br/>
|
|
98
|
+
|
|
99
|
+
_(In some example code, you may see createStore instead, but it is now deprecated. <br />
|
|
100
|
+
IAlthough it still works and is mentioned in the Redux documentation for "plain" Redux,
|
|
101
|
+
using Redux Toolkit is the recommended approach.<br />
|
|
102
|
+
Please use `configureStore`. See more [here](https://stackoverflow.com/a/71947129/12236670).)_
|
|
103
|
+
|
|
104
|
+
The single root reducer function is responsible for handling all dispatched actions and
|
|
105
|
+
determining the entire new state result each time an action occurs.
|
|
106
|
+
|
|
107
|
+
Let's start by creating a `coursesReducer` in `reducer.js` file for our application.
|
|
108
|
+
|
|
109
|
+
Every reducer requires an initial state.
|
|
110
|
+
After defining the initial state, we can draft a framework for the logic inside the reducer function:
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
import { SAVE_COURSES, DELETE_COURSE, ADD_COURSE } from './types.js'
|
|
114
|
+
|
|
115
|
+
export const coursesInitialState = [];
|
|
116
|
+
// Use the initialState as a default value
|
|
117
|
+
export const coursesReducer = (state = coursesInitialState, action) => {
|
|
118
|
+
// The reducer normally looks at the action type field to decide what happens
|
|
119
|
+
switch (action.type) {
|
|
120
|
+
// Do something here based on the different types of actions
|
|
121
|
+
default:
|
|
122
|
+
// If this reducer doesn't recognize the action type, or doesn't
|
|
123
|
+
// care about this specific action, return the existing state unchanged
|
|
124
|
+
return state
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Following that, let's implement the logic to handle the `SAVE_COURSES` and `ADD_COURSE` actions.
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
export const coursesReducer = (state = coursesInitialState, action) => {
|
|
133
|
+
switch (action.type) {
|
|
134
|
+
// in this case we need to return
|
|
135
|
+
case SAVE_COURSES:
|
|
136
|
+
return action.payload;
|
|
137
|
+
|
|
138
|
+
case ADD_COURSE:
|
|
139
|
+
return [...state, action.payload];
|
|
140
|
+
|
|
141
|
+
default:
|
|
142
|
+
return state;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Continue implementing the logic for each action as needed.
|
|
148
|
+
|
|
149
|
+
### Creating the Root Reducer
|
|
150
|
+
|
|
151
|
+
You might have numerous reducers, but it's necessary to create a single root reducer to combine all of them.
|
|
152
|
+
|
|
153
|
+
**Create the root reducer using `combineReducers`.**
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import { combineReducers, configureStore } from "@reduxjs/toolkit";
|
|
157
|
+
|
|
158
|
+
import { coursesReducer } from "./courses/reducer.js"; // reducer that we already have
|
|
159
|
+
|
|
160
|
+
const rootReducer = combineReducers({
|
|
161
|
+
courses: coursesReducer,
|
|
162
|
+
//could be extended by another slice of reducer that respond for other part of your app
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export const store = configureStore({ reducer: rootReducer });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Create root reducer without `combineReducers`.**
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
import { combineReducers } from "@reduxjs/toolkit";
|
|
172
|
+
import { coursesReducer } from "./courses/reducer.js"; // reducer that we already have
|
|
173
|
+
|
|
174
|
+
export const store = configureStore({
|
|
175
|
+
reducer: {
|
|
176
|
+
courses: coursesReducer,
|
|
177
|
+
//could be extended by another slice of reducer that respond for other part of your app
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
:::info
|
|
183
|
+
**`reducer`**
|
|
184
|
+
If there is single function, it can be directly employed as the root reducer for the store.
|
|
185
|
+
|
|
186
|
+
If it is an object of slice reducers, like `{courses : coursesReducer, user : userReducer}`,
|
|
187
|
+
`configureStore` will automatically create the root reducer by passing this object to the
|
|
188
|
+
[Redux combineReducers utility](https://redux.js.org/api/combinereducers).
|
|
189
|
+
:::
|
|
190
|
+
|
|
191
|
+
### Rules of Reducers
|
|
192
|
+
|
|
193
|
+
**Reducers must always follow some special rules:**
|
|
194
|
+
|
|
195
|
+
✅ They should only calculate the new `state` value based on the state and action arguments.
|
|
196
|
+
|
|
197
|
+
❎ They are not allowed to modify the existing `state`.
|
|
198
|
+
|
|
199
|
+
Instead, reducers must perform immutable updates by copying the existing state and applying changes to the copied values.
|
|
200
|
+
|
|
201
|
+
❌ Reducers must not implement any asynchronous logic or other ["side effects"](https://dev.to/vonheikemen/dealing-with-side-effects-and-pure-functions-in-javascript-16mg)
|
|
202
|
+
|
|
203
|
+
A function that adheres to these rules is also referred to as a ["pure" function](get-started#three-redux-fundamental-principles), even if it's not specifically written as a reducer function.
|
|
204
|
+
|
|
205
|
+
### ❗ Courses reducer with TypeScript
|
|
206
|
+
|
|
207
|
+
Let's add type checking with TS to our example.
|
|
208
|
+
|
|
209
|
+
1. Describe the course entity type
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
type CourseType = {
|
|
213
|
+
id: string;
|
|
214
|
+
title: string;
|
|
215
|
+
description: string;
|
|
216
|
+
creationDate: string;
|
|
217
|
+
duration: number;
|
|
218
|
+
authors: string[];
|
|
219
|
+
};
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
2. Create `enum` for possible courses' action types
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const enum CoursesActionTypes {
|
|
226
|
+
SAVE_COURSES = "SAVE_COURSES",
|
|
227
|
+
ADD_COURSE = "ADD_COURSE",
|
|
228
|
+
DELETE_COURSE = "DELETE_COURSES",
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
With the use of an enum, there is no need to describe the action in an `actions.ts` file.
|
|
233
|
+
Enum serves as a self-descriptive representation of the action types.
|
|
234
|
+
|
|
235
|
+
3. Once we have actions, we need to create action creators. As an example let's create action creator for ADD_COURSE action.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
type AddNewCourseAction = {
|
|
239
|
+
type: CoursesActionTypes.ADD_COURSE;
|
|
240
|
+
payload: CourseValues;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const addNewCourseAction = (
|
|
244
|
+
courseData: CourseType,
|
|
245
|
+
): AddNewCourseAction => ({
|
|
246
|
+
type: CoursesActionTypes.ADD_COURSE,
|
|
247
|
+
payload: courseData,
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
4. Create `interface` for each action and type for courses actions
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
interface SaveCourses {
|
|
255
|
+
type: CoursesActionTypes.SAVE_COURSES;
|
|
256
|
+
payload: CourseType[];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface AddCourse {
|
|
260
|
+
type: CoursesActionTypes.ADD_COURSE;
|
|
261
|
+
payload: CourseType;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
interface DeleteCourse {
|
|
265
|
+
type: CoursesActionTypes.DELETE_COURSE;
|
|
266
|
+
payload: string;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
type CoursesAction = SaveCourses | AddCourse | DeleteCourse;
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
5. It's time to write the reducer:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
const initCoursesState = [] as CourseType[];
|
|
276
|
+
|
|
277
|
+
export function coursesReducer(
|
|
278
|
+
state = initCoursesState,
|
|
279
|
+
action: CoursesAction,
|
|
280
|
+
) {
|
|
281
|
+
switch (action.type) {
|
|
282
|
+
case CoursesActionTypes.SAVE_COURSES:
|
|
283
|
+
return action.payload;
|
|
284
|
+
case CoursesActionTypes.ADD_COURSE:
|
|
285
|
+
return [...state, action.payload];
|
|
286
|
+
|
|
287
|
+
default:
|
|
288
|
+
return state;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Store
|
|
294
|
+
|
|
295
|
+
### Function `createStore`
|
|
296
|
+
|
|
297
|
+
Now let's create store for COURSES application.
|
|
298
|
+
|
|
299
|
+
```jsx
|
|
300
|
+
// store/index.js
|
|
301
|
+
|
|
302
|
+
import { createStore } from "redux";
|
|
303
|
+
|
|
304
|
+
import rootReducer from "./rootReducer";
|
|
305
|
+
import { coursesInitialState } from "./courses/reducer.js";
|
|
306
|
+
|
|
307
|
+
const appInitialState = {
|
|
308
|
+
courses: coursesInitialState,
|
|
309
|
+
};
|
|
310
|
+
const store = createStore(rootReducer, appInitialState);
|
|
311
|
+
|
|
312
|
+
export default store;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The `createStore` function accept 3 arguments:
|
|
316
|
+
|
|
317
|
+
1. `rootReducer` (we have created it early).
|
|
318
|
+
|
|
319
|
+
2. `initialState (optional)` - Represents the initial structure of your app's state.
|
|
320
|
+
If you created a `rootReducer` using `combineReducers`, this must be a plain object with the same shape
|
|
321
|
+
as the keys passed to it. Otherwise, you are free to pass any data type that your reducer can process.
|
|
322
|
+
|
|
323
|
+
3. The store enhancer (optional) - You have the option to specify a store enhancer to augment the store with
|
|
324
|
+
third-party features such as middleware, time travel, persistence, and more.
|
|
325
|
+
The only store enhancer that ships with Redux is [`applyMiddleware()`](https://redux.js.org/api/applymiddleware)(this function is described in the [next module](/docs/module-4/redux-thunk#how-to-add-and-configure-thunk)).
|
|
326
|
+
|
|
327
|
+
Add store to the entire COURSES application:
|
|
328
|
+
|
|
329
|
+
1. Wrap `App` into the `Provider`
|
|
330
|
+
2. Pass our store to the `Provider`
|
|
331
|
+
|
|
332
|
+
```jsx
|
|
333
|
+
// src/index.js
|
|
334
|
+
import ReactDOM from "react-dom";
|
|
335
|
+
|
|
336
|
+
import { Provider } from "react-redux";
|
|
337
|
+
import store from "./store";
|
|
338
|
+
import App from "./App";
|
|
339
|
+
|
|
340
|
+
import "./index.css";
|
|
341
|
+
|
|
342
|
+
const root = ReactDOM.createRoot(document.getElementById("root"));
|
|
343
|
+
|
|
344
|
+
root.render(
|
|
345
|
+
<Provider store={store}>
|
|
346
|
+
<React.StrictMode>
|
|
347
|
+
<App />
|
|
348
|
+
</React.StrictMode>
|
|
349
|
+
</Provider>,
|
|
350
|
+
);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Function `configureStore`
|
|
354
|
+
|
|
355
|
+
`configureStore` is the recommended way to create store. It is an abstraction under `createStore` function.
|
|
356
|
+
|
|
357
|
+
It helps to simplify store setup by:
|
|
358
|
+
|
|
359
|
+
1. Utilizing an options object with "named" parameters can enhance readability and make it easier to understand when working with function arguments.
|
|
360
|
+
2. By allowing you to supply arrays of middleware and enhancers to be added to the store, Redux handles the calls to `applyMiddleware` and `compose` automatically, streamlining the store configuration process.
|
|
361
|
+
3. Enables the Redux DevTools Extension automatically, making it easier to debug your application's state and actions.
|
|
362
|
+
In addition, `configureStore` adds some middleware by default, each with a specific goal:
|
|
363
|
+
4. [**Redux-thunk**](https://github.com/reduxjs/redux-thunk) is the most commonly used middleware for handling both synchronous and asynchronous logic outside of components.
|
|
364
|
+
5. In development, middleware checks for common mistakes, such as state mutation or the use of non-serializable values.
|
|
365
|
+
This results in shorter and more readable store setup code and provides good default behavior right out of the box.
|
|
366
|
+
The simplest way to utilize it is by passing the root reducer function as a parameter named reducer:
|
|
367
|
+
|
|
368
|
+
```js
|
|
369
|
+
import { configureStore } from "@reduxjs/toolkit";
|
|
370
|
+
import rootReducer from "./rootReducer";
|
|
371
|
+
|
|
372
|
+
const store = configureStore({
|
|
373
|
+
reducer: rootReducer,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
export default store;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Redux DevTools
|
|
380
|
+
|
|
381
|
+
:::info
|
|
382
|
+
Redux DevTools is enabled by default in case of using `configureStore` (redux/toolkit).
|
|
383
|
+
This upcoming section is for when using reac-redux without toolkit.
|
|
384
|
+
:::
|
|
385
|
+
|
|
386
|
+
```npm
|
|
387
|
+
npm i redux-devtools-extension
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Let's look at the store with DevTools applied example.
|
|
391
|
+
|
|
392
|
+
```jsx
|
|
393
|
+
// store/index.js
|
|
394
|
+
|
|
395
|
+
import { composeWithDevTools } from "redux-devtools-extension"; // import DevTools
|
|
396
|
+
import { createStore } from "redux";
|
|
397
|
+
import rootReducer from "./rootReducer";
|
|
398
|
+
import { coursesInitialState } from "./courses/reducer.js";
|
|
399
|
+
|
|
400
|
+
const appInitialState = {
|
|
401
|
+
courses: coursesInitialState,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const store = createStore(
|
|
405
|
+
rootReducer,
|
|
406
|
+
appInitialState,
|
|
407
|
+
composeWithDevTools(), //apply React DevTools enhancer
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
export default store;
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
After this, React DevTools will have access to you web application.
|
|
414
|
+
Go to the Chrome DevTools and find the tab `Redux`.
|
|
415
|
+

|
|
416
|
+
|
|
417
|
+
[Link to the Chrome Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd)
|
|
418
|
+
|
|
419
|
+
## Data Flow
|
|
420
|
+
|
|
421
|
+
- Initial setup:
|
|
422
|
+
- A Redux store is created by using a root reducer function.
|
|
423
|
+
- The store invokes the root reducer once and stores the returned value as its initial state.
|
|
424
|
+
- When the UI is initially rendered, UI components access the current state of the Redux store and utilize that data to determine what to render on the screen. Additionally, UI components subscribe to future store updates, allowing them to detect any changes in the state and respond accordingly.
|
|
425
|
+
- Updates:
|
|
426
|
+
- Something happens in the app, such as a user add new course.
|
|
427
|
+
- The app code dispatches an `action` to the Redux store, like:
|
|
428
|
+
```jsx
|
|
429
|
+
onClick((event) => dispatch(addCoursesAction(newCourse)));
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
- The store executes the reducer function again, passing the previous `state` and the current action. It then saves the returned value as the new `state`.
|
|
433
|
+
- The store informs all subscribed UI components that an update has taken place in the store's state.
|
|
434
|
+
- Each UI component that relies on data from the store verifies if the state parts they require have undergone changes.
|
|
435
|
+
- Any component that detects changes in its data triggers a re-render using the new data, updating the content displayed on the screen accordingly.
|
|
436
|
+
|
|
437
|
+
Here's what that data flow looks like visually:
|
|
438
|
+

|
|
439
|
+
|
|
440
|
+
## Materials
|
|
441
|
+
|
|
442
|
+
1. [Redux Fundamentals: State, Actions, and Reducers.](https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers#designing-the-state-structure)
|
|
443
|
+
2. [What is Redux Toolkit?](https://redux.js.org/redux-toolkit/overview)
|
|
444
|
+
3. [Modern Redux with Redux Toolkit.](https://redux.js.org/tutorials/fundamentals/part-8-modern-redux)
|
|
445
|
+
4. [Redux Data Flow.](https://redux.js.org/tutorials/essentials/part-3-data-flow)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
sidebar_label: 'COMPONENTS'
|
|
4
|
+
title: 'Components description for Tasks 4'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## App
|
|
8
|
+
- Use the Redux Thunk for `courses/all` and `authors/all` GET requests.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Store
|
|
13
|
+
- Add a new value `role` to the initial state of user
|
|
14
|
+
```js
|
|
15
|
+
const userInitialState = {
|
|
16
|
+
isAuth: '',
|
|
17
|
+
name: '',
|
|
18
|
+
email: '',
|
|
19
|
+
token: '',
|
|
20
|
+
role: '', // new value
|
|
21
|
+
};
|
|
22
|
+
```
|
|
23
|
+
---
|
|
24
|
+
## Courses component
|
|
25
|
+
- Make a request to get current user `/users/me`. Now you have `name`, `email` and `role` values.
|
|
26
|
+
Save this data to the store. Use **Thunk** for it.
|
|
27
|
+
If you have user's data you should redirect him to the `\courses`.
|
|
28
|
+
|
|
29
|
+
:::tip
|
|
30
|
+
Don't forget to add header `Authorization: token`.
|
|
31
|
+
:::
|
|
32
|
+
|
|
33
|
+
- Button `Add new course` and route `/courses/add` **should be available only for ADMIN role**.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
## Private route (new component)
|
|
37
|
+
### Create [`PrivateRoute` component](/docs/module-4/private-routes).
|
|
38
|
+
- This component should render any component as a children.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
- This component checks user's role and if it is **ADMIN** `PrivateRoute` returns
|
|
42
|
+
component as children otherwise redirects to `/courses`.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
## Header
|
|
46
|
+
### Add new functionality for the `logout` button
|
|
47
|
+
- When user clicks `logout` button you should make a request to logout.
|
|
48
|
+
```
|
|
49
|
+
Swagger:
|
|
50
|
+
url: "/logout",
|
|
51
|
+
method: "DELETE".
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
## `EmptyCourseList` component
|
|
56
|
+
### Implement a new feature for `EmptyCourseList` component:
|
|
57
|
+
FOR ADMIN USER:
|
|
58
|
+
- By clicking 'ADD NEW COURSE' button user should be navigated to the route `courses/add`.
|
|
59
|
+
|
|
60
|
+
FOR NON-ADMINS (REGULAR USERS):
|
|
61
|
+
- Message "You don't have permissions to create a course. Please log in as ADMIN" is displayed instead of 'ADD NEW COURSE' button.
|
|
62
|
+
___
|
|
63
|
+
|
|
64
|
+
## CourseCard component
|
|
65
|
+
:::caution
|
|
66
|
+
This components has different functionality for users with different roles.
|
|
67
|
+
:::
|
|
68
|
+
|
|
69
|
+
*CourseCard component without ADMIN role:*
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
*CourseCard component with ADMIN role:*
|
|
74
|
+

|
|
75
|
+
|
|
76
|
+
## Course card with ADMIN role
|
|
77
|
+
|
|
78
|
+
When user has a role **ADMIN** `CourseCard` two new buttons should be displayed:
|
|
79
|
+
1. `Delete course` button:
|
|
80
|
+
When user clicks this button - course should be deleted from back-end.
|
|
81
|
+
Delete this course from store after success response. Use Thunk for it.
|
|
82
|
+
```
|
|
83
|
+
Swagger:
|
|
84
|
+
url: "/courses/:id",
|
|
85
|
+
method: "DELETE".
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
2. `Update course` button:
|
|
89
|
+
When user clicks this button **Update course** form is opened by the route `/courses/update/:courseId`.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
## Add new course
|
|
93
|
+
### Implement new features for CourseForm component:
|
|
94
|
+
|
|
95
|
+
- `CourseForm` should be displayed only for user with a role **ADMIN**.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
- Use `PrivateRoure` for `/courses/add` route.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
- When user clicks the `Create course` button:
|
|
102
|
+
- New course should be sent to back-end.
|
|
103
|
+
```
|
|
104
|
+
Swagger:
|
|
105
|
+
url: "/courses/add",
|
|
106
|
+
method: "POST".
|
|
107
|
+
```
|
|
108
|
+
- Save the response course result to the **store**.
|
|
109
|
+
|
|
110
|
+
- Navigate to the `Courses` component.
|
|
111
|
+
|
|
112
|
+
- New course should be in the courses list.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
- When user clicks the `Create author` button:
|
|
116
|
+
- New author should be sent to back-end.
|
|
117
|
+
```
|
|
118
|
+
Swagger:
|
|
119
|
+
url: "/authors/add",
|
|
120
|
+
method: "POST".
|
|
121
|
+
```
|
|
122
|
+
- Save the response author result to the **store**.
|
|
123
|
+
|
|
124
|
+
- New author should be in the all authors list.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
:::info
|
|
128
|
+
Now you do not have to generate **id** for new course and author and **creation date**.
|
|
129
|
+
API generates id for new course and author automatically.
|
|
130
|
+
:::
|
|
131
|
+
|
|
132
|
+
Models of course and author info must be as bellow:
|
|
133
|
+
```js
|
|
134
|
+
// Course
|
|
135
|
+
{
|
|
136
|
+
title: string
|
|
137
|
+
description: string
|
|
138
|
+
duration: number
|
|
139
|
+
authors: [authorId]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Author
|
|
143
|
+
{
|
|
144
|
+
name: string
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Update course
|
|
150
|
+
|
|
151
|
+
### Now you should use `CourseFrom` (old `CreateCourse`) for course creation and course updating.
|
|
152
|
+
When user clicks `Update` button on the `CourseCard`:
|
|
153
|
+
|
|
154
|
+
- `CourseForm` is rendered instead of the Courses component by route `/courses/update/:courseId`
|
|
155
|
+
(`courseId` - id of the course that should be updated).
|
|
156
|
+
(Use `useParams` hook to get courseId).
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
- All fields on the `CourseForm` page have prefilled data from opened course.
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
- Route `/courses/update/:courseId` should be accessible only for a role **ADMIN**.
|
|
164
|
+
(Use **Private route** component).
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
- After clicking on the `Update course` button, the course should be updated on API.
|
|
168
|
+
```
|
|
169
|
+
Swagger:
|
|
170
|
+
url: "/courses/:id",
|
|
171
|
+
method: "PUT".
|
|
172
|
+
```
|
|
173
|
+
A returned from API course should be added to **store** as updated course.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
- When user clicks the `Create author` button:
|
|
177
|
+
- New author should be sent to the back-end.
|
|
178
|
+
```
|
|
179
|
+
Swagger:
|
|
180
|
+
url: "/authors/add",
|
|
181
|
+
method: "POST".
|
|
182
|
+
```
|
|
183
|
+
- Save the response author result to the **store**.
|
|
184
|
+
|
|
185
|
+
- New author should be in the all authors list.
|
|
186
|
+
___
|
|
187
|
+
|