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,87 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { Courses } from "../components/Courses/Courses";
|
|
4
|
+
|
|
5
|
+
describe('Courses component', () => {
|
|
6
|
+
const mockedCoursesList = [
|
|
7
|
+
{
|
|
8
|
+
id: "1",
|
|
9
|
+
title: "Course 1",
|
|
10
|
+
description: "Course 1 description",
|
|
11
|
+
creationDate: "2022-01-01",
|
|
12
|
+
duration: 60,
|
|
13
|
+
authors: [
|
|
14
|
+
"27cc3006-e93a-4748-8ca8-73d06aa93b6d",
|
|
15
|
+
"f762978b-61eb-4096-812b-ebde22838167",
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "2",
|
|
20
|
+
title: "Course 2",
|
|
21
|
+
description: "Course 2 description",
|
|
22
|
+
creationDate: "2022-02-01",
|
|
23
|
+
duration: 90,
|
|
24
|
+
authors: ["df32994e-b23d-497c-9e4d-84e4dc02882f"],
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
const onAddClick = jest.fn();
|
|
28
|
+
|
|
29
|
+
test("Should render list of courses", () => {
|
|
30
|
+
render(<Courses courseList={mockedCoursesList} onAddClick={onAddClick} setSelectedCourseId={(id) => {}}/>);
|
|
31
|
+
|
|
32
|
+
const courseElements = screen.getAllByTestId("courseCard");
|
|
33
|
+
|
|
34
|
+
expect(courseElements[0]).toBeInTheDocument();
|
|
35
|
+
expect(courseElements).toHaveLength(2);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("Should render 'Add new course' button", () => {
|
|
39
|
+
render(<Courses courseList={mockedCoursesList} onAddClick={onAddClick} setSelectedCourseId={(id) => {}}/>);
|
|
40
|
+
|
|
41
|
+
const button = screen.getByText(/Add new course/);
|
|
42
|
+
|
|
43
|
+
expect(button).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("Should render EmptyCourseList component if no courses", () => {
|
|
47
|
+
const mockedEmptyCoursesList: [] = [];
|
|
48
|
+
render(<Courses courseList={mockedEmptyCoursesList} onAddClick={onAddClick} setSelectedCourseId={(id) => {}}/>);
|
|
49
|
+
|
|
50
|
+
const emptyContainerElement = screen.getByTestId("emptyContainer");
|
|
51
|
+
const addButtonElement = screen.getByTestId("addCourse");
|
|
52
|
+
|
|
53
|
+
expect(emptyContainerElement).toBeInTheDocument();
|
|
54
|
+
expect(addButtonElement).toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('EmptyCourseList', () => {
|
|
59
|
+
const mockedEmptyCoursesList: [] = [];
|
|
60
|
+
const onAddClick = jest.fn();
|
|
61
|
+
|
|
62
|
+
test("Should render title", () => {
|
|
63
|
+
render(<Courses courseList={mockedEmptyCoursesList} onAddClick={onAddClick} setSelectedCourseId={(id) => {}}/>);
|
|
64
|
+
|
|
65
|
+
const title = screen.getByText(/Your List Is Empty/);
|
|
66
|
+
|
|
67
|
+
expect(title).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("Should render subtitle", () => {
|
|
71
|
+
render(<Courses courseList={mockedEmptyCoursesList} onAddClick={onAddClick} setSelectedCourseId={(id) => {}}/>);
|
|
72
|
+
|
|
73
|
+
const subTitle = screen.getByText(/Please use/);
|
|
74
|
+
|
|
75
|
+
expect(subTitle).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("Should render button", () => {
|
|
79
|
+
render(<Courses courseList={mockedEmptyCoursesList} onAddClick={onAddClick} setSelectedCourseId={(id) => {}}/>);
|
|
80
|
+
|
|
81
|
+
const button = screen.getByText('ADD NEW COURSE');
|
|
82
|
+
|
|
83
|
+
expect(button).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { render, fireEvent, screen } from "@testing-library/react";
|
|
3
|
+
import { CreateAuthor } from "../components/CreateCourse/components/CreateAuthor/CreateAuthor";
|
|
4
|
+
|
|
5
|
+
describe("CreateAuthor", () => {
|
|
6
|
+
it("should call onCreateAuthor when create button is clicked with valid name", () => {
|
|
7
|
+
const onCreateAuthorMock = jest.fn();
|
|
8
|
+
render(<CreateAuthor onCreateAuthor={onCreateAuthorMock} />);
|
|
9
|
+
|
|
10
|
+
const nameInput = screen.getByTestId(
|
|
11
|
+
"createAuthorInput"
|
|
12
|
+
) as HTMLInputElement;
|
|
13
|
+
const createButton = screen.getByTestId("createAuthorButton");
|
|
14
|
+
|
|
15
|
+
// Enter a valid name in the input field
|
|
16
|
+
fireEvent.change(nameInput, { target: { value: "John" } });
|
|
17
|
+
|
|
18
|
+
// Click the create button
|
|
19
|
+
fireEvent.click(createButton);
|
|
20
|
+
|
|
21
|
+
// Assert that onCreateAuthor is called with the correct data
|
|
22
|
+
expect(onCreateAuthorMock).toHaveBeenCalledWith({
|
|
23
|
+
name: "John",
|
|
24
|
+
id: expect.any(String),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Assert that the input field is cleared
|
|
28
|
+
expect(nameInput.value).toBe("");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should not call onCreateAuthor when create button is clicked with an empty name", () => {
|
|
32
|
+
const onCreateAuthorMock = jest.fn();
|
|
33
|
+
render(<CreateAuthor onCreateAuthor={onCreateAuthorMock} />);
|
|
34
|
+
|
|
35
|
+
const createButton = screen.getByTestId("createAuthorButton");
|
|
36
|
+
|
|
37
|
+
// Click the create button without entering a name
|
|
38
|
+
|
|
39
|
+
fireEvent.click(createButton);
|
|
40
|
+
|
|
41
|
+
// Assert that onCreateAuthor is not called
|
|
42
|
+
expect(onCreateAuthorMock).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { render, fireEvent, screen } from "@testing-library/react";
|
|
3
|
+
import { CreateCourse } from "../components/CreateCourse/CreateCourse";
|
|
4
|
+
|
|
5
|
+
describe("CreateCourse", () => {
|
|
6
|
+
const mockAuthors = [
|
|
7
|
+
{ id: "1", name: "Author 1" },
|
|
8
|
+
{ id: "2", name: "Author 2" },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const mockCreateCourse = jest.fn();
|
|
12
|
+
const mockCreateAuthor = jest.fn();
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockCreateCourse.mockClear();
|
|
16
|
+
mockCreateAuthor.mockClear();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should create a new course with the entered data", () => {
|
|
20
|
+
render(
|
|
21
|
+
<CreateCourse
|
|
22
|
+
authorsList={mockAuthors}
|
|
23
|
+
createCourse={mockCreateCourse}
|
|
24
|
+
createAuthor={mockCreateAuthor}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const titleInput = screen.getByTestId("titleInput");
|
|
29
|
+
const durationInput = screen.getByTestId("durationInput");
|
|
30
|
+
const descriptionTextArea = screen.getByTestId("descriptionTextArea");
|
|
31
|
+
const createCourseButton = screen.getByTestId("createCourseButton");
|
|
32
|
+
|
|
33
|
+
// Enter the course title
|
|
34
|
+
fireEvent.change(titleInput, { target: { value: "Course Title" } });
|
|
35
|
+
fireEvent.change(durationInput, { target: { value: "20" } });
|
|
36
|
+
|
|
37
|
+
// Enter the course description
|
|
38
|
+
fireEvent.change(descriptionTextArea, {
|
|
39
|
+
target: { value: "Course Description" },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Click the create course button
|
|
43
|
+
fireEvent.click(createCourseButton);
|
|
44
|
+
|
|
45
|
+
// Assert that createCourse is called with the correct data
|
|
46
|
+
expect(mockCreateCourse).toHaveBeenCalledWith({
|
|
47
|
+
id: expect.any(String),
|
|
48
|
+
title: "Course Title",
|
|
49
|
+
description: "Course Description",
|
|
50
|
+
creationDate: "",
|
|
51
|
+
duration: 20,
|
|
52
|
+
authors: [],
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should not create a new course with the empty data", () => {
|
|
57
|
+
render(
|
|
58
|
+
<CreateCourse
|
|
59
|
+
authorsList={mockAuthors}
|
|
60
|
+
createCourse={mockCreateCourse}
|
|
61
|
+
createAuthor={mockCreateAuthor}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const createCourseButton = screen.getByTestId("createCourseButton");
|
|
66
|
+
|
|
67
|
+
// Click the create course button
|
|
68
|
+
fireEvent.click(createCourseButton);
|
|
69
|
+
|
|
70
|
+
// Assert that createCourse is called with the correct data
|
|
71
|
+
expect(mockCreateCourse).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should add an author to the course when CreateAuthor component creates a new author", () => {
|
|
75
|
+
render(
|
|
76
|
+
<CreateCourse
|
|
77
|
+
authorsList={mockAuthors}
|
|
78
|
+
createCourse={mockCreateCourse}
|
|
79
|
+
createAuthor={mockCreateAuthor}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const createAuthorButton = screen.getByTestId("createAuthorButton");
|
|
84
|
+
const nameInput = screen.getByTestId(
|
|
85
|
+
"createAuthorInput"
|
|
86
|
+
) as HTMLInputElement;
|
|
87
|
+
|
|
88
|
+
fireEvent.change(nameInput, { target: { value: "John" } });
|
|
89
|
+
|
|
90
|
+
fireEvent.click(createAuthorButton);
|
|
91
|
+
|
|
92
|
+
expect(mockCreateAuthor).toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should display the course authors in the course", () => {
|
|
96
|
+
const mockCourseAuthors = ["1", "2"];
|
|
97
|
+
render(
|
|
98
|
+
<CreateCourse
|
|
99
|
+
authorsList={mockAuthors}
|
|
100
|
+
createCourse={mockCreateCourse}
|
|
101
|
+
createAuthor={mockCreateAuthor}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const courseAuthorItems = screen.getAllByTestId("authorItem");
|
|
106
|
+
expect(courseAuthorItems.length).toBe(mockCourseAuthors.length);
|
|
107
|
+
});
|
|
108
|
+
it("should display the empty authors list message when there are no authors", () => {
|
|
109
|
+
render(
|
|
110
|
+
<CreateCourse
|
|
111
|
+
authorsList={[]}
|
|
112
|
+
createCourse={mockCreateCourse}
|
|
113
|
+
createAuthor={mockCreateAuthor}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
const emptyText = screen.getByTestId("emptyText");
|
|
117
|
+
|
|
118
|
+
// Assert that the empty text is displayed
|
|
119
|
+
expect(emptyText).toBeInTheDocument();
|
|
120
|
+
expect(emptyText.textContent).toBe("Authors list is empty");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
sidebar_label: 'Redux Hooks'
|
|
4
|
+
title: 'Redux Hooks'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
As a React, Redux also has hooks.
|
|
8
|
+
In this topic, we will look at the hooks that are used more often and that will be needed to do your homework.
|
|
9
|
+
|
|
10
|
+
## useSelector()
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
const result: any = useSelector(selector: Function, equalityFn?: Function);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Allows you to extract data from the Redux store state, using a selector function.
|
|
17
|
+
|
|
18
|
+
:::info
|
|
19
|
+
The selector function should be [pure](https://en.wikipedia.org/wiki/Pure_function) as it might be executed multiple times and at arbitrary moments. A pure function ensures consistent output and no side effects, maintaining the stability of the application.
|
|
20
|
+
:::
|
|
21
|
+
|
|
22
|
+
The *selector* is called with the entire Redux store state as its sole argument. The selector runs whenever the function component renders. However, if its reference has not changed since the previous render, the hook returns a cached result without re-running the selector.
|
|
23
|
+
|
|
24
|
+
`useSelector()` subscribes to the Redux store and executes your selector whenever an action is dispatched.
|
|
25
|
+
|
|
26
|
+
Some specifics of `useSelector` work:
|
|
27
|
+
|
|
28
|
+
⭐ The selector can return any value as a result. The return value of the selector will be used as the return value of the `useSelector()` hook.
|
|
29
|
+
|
|
30
|
+
⭐ When an action is dispatched, `useSelector()` performs a reference comparison between the previous selector result value and the current result value. If the values are different, the component is forced to re-render. However, if the values are the same, the component does not re-render.
|
|
31
|
+
|
|
32
|
+
⭐ `useSelector()` uses strict `===` reference equality checks by default.
|
|
33
|
+
|
|
34
|
+
⭐ You can use `useSelector()` multiple times within a single function component. Each invocation of `useSelector()` creates a separate subscription to the Redux store.
|
|
35
|
+
|
|
36
|
+
### `useSelector` Example
|
|
37
|
+
|
|
38
|
+
Basic usage:
|
|
39
|
+
|
|
40
|
+
```jsx
|
|
41
|
+
import { useSelector } from 'react-redux';
|
|
42
|
+
|
|
43
|
+
export const CounterComponent = () => {
|
|
44
|
+
const counter = useSelector((state) => state.counter);
|
|
45
|
+
|
|
46
|
+
return <div>{counter}</div>
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
Using props via closure to determine what to extract:
|
|
50
|
+
```jsx
|
|
51
|
+
import { useSelector } from 'react-redux';
|
|
52
|
+
|
|
53
|
+
export const TodoListItem = (props) => {
|
|
54
|
+
const todo = useSelector((state) => state.todos[props.id]);
|
|
55
|
+
|
|
56
|
+
return <div>{todo.text}</div>
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
:::tip
|
|
61
|
+
It's a good practice to create a separate callback function and pass it to `useSelector()` to improve code readability and maintainability.
|
|
62
|
+
```jsx
|
|
63
|
+
// selectors.js
|
|
64
|
+
export const getCounter = state => state.counter;
|
|
65
|
+
|
|
66
|
+
// CounterComponent.jsx
|
|
67
|
+
import React from 'react'
|
|
68
|
+
import { useSelector } from 'react-redux'
|
|
69
|
+
|
|
70
|
+
import { getCounter } from './selectors';
|
|
71
|
+
|
|
72
|
+
export const CounterComponent = () => {
|
|
73
|
+
const counter = useSelector(getCounter) //you don't need to call callback
|
|
74
|
+
return <div>{counter}</div>
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
:::
|
|
78
|
+
|
|
79
|
+
## useDispatch()
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const dispatch = useDispatch()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This hook provides a reference to the `dispatch` function from the Redux store, allowing you to dispatch actions when needed.
|
|
86
|
+
|
|
87
|
+
In the previous topic, you learned that updating the store requires the use of `actions`. Now, let's see how we can achieve this using `dispatch`.
|
|
88
|
+
|
|
89
|
+
### `useDispatch` Example
|
|
90
|
+
|
|
91
|
+
```jsx
|
|
92
|
+
import React from 'react';
|
|
93
|
+
import { useDispatch } from 'react-redux';
|
|
94
|
+
|
|
95
|
+
export const CounterComponent = ({ value }) => {
|
|
96
|
+
const dispatch = useDispatch(); // create an instance
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div>
|
|
100
|
+
<span>{value}</span>
|
|
101
|
+
<button onClick={() => dispatch({ type: 'INCREMENT_COUNTER' })}>
|
|
102
|
+
Increment counter
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
Upon clicking the button, we call `dispatch` with the action 'INCREMENT_COUNTER'.
|
|
109
|
+
This action triggers the appropriate condition in the reducer, which then updates the store state accordingly.
|
|
110
|
+
|
|
111
|
+
Let's make some modification, to make our call more readable
|
|
112
|
+
|
|
113
|
+
1. Create a separate file for actions
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
// actions.js
|
|
117
|
+
|
|
118
|
+
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
|
|
119
|
+
|
|
120
|
+
export const incrementCounerAction = () => ({type: INCREMENT_COUNTER});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
2. Use `incrementCouner` in our `CounterComponent`
|
|
124
|
+
```jsx
|
|
125
|
+
// CounterComponent.jsx
|
|
126
|
+
|
|
127
|
+
import React from 'react';
|
|
128
|
+
import { useDispatch } from 'react-redux';
|
|
129
|
+
|
|
130
|
+
import { incrementCounerAction } from './actions';
|
|
131
|
+
|
|
132
|
+
export const CounterComponent = ({ value }) => {
|
|
133
|
+
const dispatch = useDispatch() // create an instance
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div>
|
|
137
|
+
<span>{value}</span>
|
|
138
|
+
<button onClick={() => dispatch(incrementCounerAction())}>
|
|
139
|
+
Increment counter
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Let's not stop and add usage of `useSelector` 😀
|
|
147
|
+
|
|
148
|
+
```jsx
|
|
149
|
+
// CounterComponent.jsx
|
|
150
|
+
|
|
151
|
+
import React from 'react';
|
|
152
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
153
|
+
|
|
154
|
+
import {incrementCounerAction} from './actions';
|
|
155
|
+
|
|
156
|
+
export const CounterComponent = () => { // remove the prop, we don't need it here
|
|
157
|
+
const value = useSelector(state => state.counter); // now we take value from the store
|
|
158
|
+
const dispatch = useDispatch();
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div>
|
|
162
|
+
<span>{value}</span>
|
|
163
|
+
<button onClick={() => dispatch(incrementCounerAction())}>
|
|
164
|
+
Increment counter
|
|
165
|
+
</button>
|
|
166
|
+
</div>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
As a result after every button click:
|
|
172
|
+
1. `dispatch` will be called with action `{type: INCREMENT_COUNTER}`.
|
|
173
|
+
2. The reducer will update the state with the new value of `counter`.
|
|
174
|
+
3. `useSelector` will return new `counter` value.
|
|
175
|
+
4. After the store is updated, the component will be re-rendered, and the new counter value will be displayed on the page.
|
|
176
|
+
|
|
177
|
+
:::tip
|
|
178
|
+
If you need to pass the `payload` to the action, you can do it like this:
|
|
179
|
+
```js
|
|
180
|
+
export const incrementCounerAction = (payload) => ({type: INCREMENT_COUNTER, payload});
|
|
181
|
+
```
|
|
182
|
+
:::
|
|
183
|
+
|
|
184
|
+
## Materials
|
|
185
|
+
1. [React Redux. Hooks (Documentation)](https://react-redux.js.org/api/hooks)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|