├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ └── codeStyleConfig.xml
├── misc.xml
├── modules.xml
└── react-redux-ts-course.iml
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.tsx
├── components
│ ├── TodoList.tsx
│ └── UserList.tsx
├── hooks
│ ├── useActions.ts
│ └── useTypedSelector.ts
├── index.tsx
├── store
│ ├── action-creators
│ │ ├── index.ts
│ │ ├── todo.ts
│ │ └── user.ts
│ ├── index.ts
│ └── reducers
│ │ ├── index.ts
│ │ ├── todoReducer.ts
│ │ └── userReducer.ts
└── types
│ ├── todo.ts
│ └── user.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/react-redux-ts-course.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Если воспользовался репозиторием, дай обратную связь - поставь звезду
2 |
3 | ### npm start - запуск
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-ts-course",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.9",
7 | "@testing-library/react": "^11.2.3",
8 | "@testing-library/user-event": "^12.6.2",
9 | "@types/jest": "^26.0.20",
10 | "@types/node": "^12.19.15",
11 | "@types/react": "^16.14.2",
12 | "@types/react-dom": "^16.9.10",
13 | "@types/react-redux": "^7.1.16",
14 | "axios": "^0.21.1",
15 | "react": "^17.0.1",
16 | "react-dom": "^17.0.1",
17 | "react-redux": "^7.2.2",
18 | "react-scripts": "4.0.1",
19 | "redux": "^4.0.5",
20 | "redux-thunk": "^2.3.0",
21 | "typescript": "^4.1.3",
22 | "web-vitals": "^0.2.4"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utimur/react-redux-typescript-course/f59eb21576daa0455bd341a779db024a4660ee74/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utimur/react-redux-typescript-course/f59eb21576daa0455bd341a779db024a4660ee74/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utimur/react-redux-typescript-course/f59eb21576daa0455bd341a779db024a4660ee74/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import UserList from "./components/UserList";
3 | import TodoList from "./components/TodoList";
4 |
5 | const App = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default App;
16 |
--------------------------------------------------------------------------------
/src/components/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from 'react';
2 | import {useTypedSelector} from "../hooks/useTypedSelector";
3 | import {useActions} from "../hooks/useActions";
4 |
5 | const TodoList: React.FC = () => {
6 | const {page, error, loading, todos, limit} = useTypedSelector(state => state.todo)
7 | const {fetchTodos, setTodoPage} = useActions()
8 | const pages = [1, 2, 3, 4, 5]
9 |
10 | useEffect(() => {
11 | fetchTodos(page, limit)
12 | }, [page])
13 |
14 | if (loading) {
15 | return Идет загрузка...
16 | }
17 | if (error) {
18 | return {error}
19 | }
20 |
21 | return (
22 |
23 | {todos.map(todo =>
24 |
{todo.id} - {todo.title}
25 | )}
26 |
27 | {pages.map(p =>
28 |
setTodoPage(p)}
30 | style={{border:p === page ? '2px solid green' : '1px solid gray', padding: 10}}
31 | >
32 | {p}
33 |
34 | )}
35 |
36 |
37 | );
38 | };
39 |
40 | export default TodoList;
41 |
--------------------------------------------------------------------------------
/src/components/UserList.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from 'react';
2 | import {useTypedSelector} from "../hooks/useTypedSelector";
3 | import {fetchUsers} from "../store/action-creators/user";
4 | import {useActions} from "../hooks/useActions";
5 |
6 | const UserList: React.FC = () => {
7 | const {users, error, loading} = useTypedSelector(state => state.user)
8 | const {fetchUsers} = useActions()
9 |
10 | useEffect(() => {
11 | fetchUsers()
12 | }, [])
13 |
14 | if (loading) {
15 | return Идет загрузка...
16 | }
17 | if (error) {
18 | return {error}
19 | }
20 |
21 | return (
22 |
23 | {users.map(user =>
24 |
{user.name}
25 | )}
26 |
27 | );
28 | };
29 |
30 | export default UserList;
31 |
--------------------------------------------------------------------------------
/src/hooks/useActions.ts:
--------------------------------------------------------------------------------
1 | import {useDispatch} from "react-redux";
2 | import {bindActionCreators} from "redux";
3 | import ActionCreators from '../store/action-creators/'
4 |
5 | export const useActions = () => {
6 | const dispatch = useDispatch()
7 | return bindActionCreators(ActionCreators, dispatch)
8 | }
9 |
--------------------------------------------------------------------------------
/src/hooks/useTypedSelector.ts:
--------------------------------------------------------------------------------
1 | import {TypedUseSelectorHook, useSelector} from "react-redux";
2 | import {RootState} from "../store/reducers";
3 |
4 |
5 | export const useTypedSelector: TypedUseSelectorHook = useSelector
6 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import {Provider} from "react-redux";
5 | import {store} from "./store";
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 |
--------------------------------------------------------------------------------
/src/store/action-creators/index.ts:
--------------------------------------------------------------------------------
1 | import * as UserActionCreators from './user'
2 | import * as TodoActionCreators from './todo'
3 |
4 | export default {
5 | ...TodoActionCreators,
6 | ...UserActionCreators
7 | }
8 |
--------------------------------------------------------------------------------
/src/store/action-creators/todo.ts:
--------------------------------------------------------------------------------
1 | import {Dispatch} from "redux";
2 | import axios from "axios";
3 | import {TodoAction, TodoActionTypes} from "../../types/todo";
4 |
5 | export const fetchTodos = (page = 1, limit = 10) => {
6 | return async (dispatch: Dispatch) => {
7 | try {
8 | dispatch({type: TodoActionTypes.FETCH_TODOS})
9 | const response = await axios.get('https://jsonplaceholder.typicode.com/todos', {
10 | params: {_page: page, _limit: limit}
11 | })
12 | setTimeout(() => {
13 | dispatch({type: TodoActionTypes.FETCH_TODOS_SUCCESS, payload: response.data})
14 | }, 500)
15 | } catch (e) {
16 | dispatch({
17 | type: TodoActionTypes.FETCH_TODOS_ERROR,
18 | payload: 'Произошла ошибка при загрузке списка дел'
19 | })
20 | }
21 | }
22 | }
23 | export function setTodoPage(page: number): TodoAction {
24 | return {type: TodoActionTypes.SET_TODO_PAGE, payload: page}
25 | }
26 |
--------------------------------------------------------------------------------
/src/store/action-creators/user.ts:
--------------------------------------------------------------------------------
1 | import {UserAction, UserActionTypes} from "../../types/user";
2 | import {Dispatch} from "redux";
3 | import axios from "axios";
4 |
5 | export const fetchUsers = () => {
6 | return async (dispatch: Dispatch) => {
7 | try {
8 | dispatch({type: UserActionTypes.FETCH_USERS})
9 | const response = await axios.get('https://jsonplaceholder.typicode.com/users')
10 | setTimeout(() => {
11 | dispatch({type: UserActionTypes.FETCH_USERS_SUCCESS, payload: response.data})
12 | }, 500)
13 | } catch (e) {
14 | dispatch({
15 | type: UserActionTypes.FETCH_USERS_ERROR,
16 | payload: 'Произошла ошибка при загрузке пользователей'
17 | })
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import {applyMiddleware, createStore} from "redux";
2 | import thunk from "redux-thunk";
3 | import {rootReducer} from "./reducers";
4 |
5 |
6 | export const store = createStore(rootReducer, applyMiddleware(thunk))
7 |
--------------------------------------------------------------------------------
/src/store/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import {combineReducers} from "redux";
2 | import {userReducer} from "./userReducer";
3 | import {todoReducer} from "./todoReducer";
4 |
5 |
6 | export const rootReducer = combineReducers({
7 | user: userReducer,
8 | todo: todoReducer
9 | })
10 |
11 | export type RootState = ReturnType
12 |
--------------------------------------------------------------------------------
/src/store/reducers/todoReducer.ts:
--------------------------------------------------------------------------------
1 | import {TodoAction, TodoActionTypes, TodoState} from "../../types/todo";
2 |
3 | const initialState: TodoState = {
4 | todos: [],
5 | page: 1,
6 | error: null,
7 | limit: 10,
8 | loading: false
9 | }
10 |
11 | export const todoReducer = (state = initialState, action: TodoAction): TodoState => {
12 | switch (action.type) {
13 | case TodoActionTypes.FETCH_TODOS:
14 | return {...state, loading: true}
15 | case TodoActionTypes.FETCH_TODOS_SUCCESS:
16 | return {...state, loading: false, todos: action.payload}
17 | case TodoActionTypes.FETCH_TODOS_ERROR:
18 | return {...state, loading: false, error: action.payload}
19 | case TodoActionTypes.SET_TODO_PAGE:
20 | return {...state, page: action.payload}
21 | default:
22 | return state
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/store/reducers/userReducer.ts:
--------------------------------------------------------------------------------
1 | import {UserAction, UserActionTypes, UserState} from "../../types/user";
2 |
3 | const initialState: UserState = {
4 | users: [],
5 | loading: false,
6 | error: null
7 | }
8 |
9 | export const userReducer = (state = initialState, action: UserAction): UserState => {
10 | switch (action.type) {
11 | case UserActionTypes.FETCH_USERS:
12 | return {loading: true, error: null, users: []}
13 | case UserActionTypes.FETCH_USERS_SUCCESS:
14 | return {loading: false, error: null, users: action.payload}
15 | case UserActionTypes.FETCH_USERS_ERROR:
16 | return {loading: false, error: action.payload, users: []}
17 | default:
18 | return state
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/types/todo.ts:
--------------------------------------------------------------------------------
1 | export interface TodoState {
2 | todos: any[];
3 | loading: boolean;
4 | error: null | string;
5 | page: number;
6 | limit: number;
7 | }
8 |
9 | export enum TodoActionTypes {
10 | FETCH_TODOS= 'FETCH_TODOS',
11 | FETCH_TODOS_SUCCESS= 'FETCH_TODOS_SUCCESS',
12 | FETCH_TODOS_ERROR= 'FETCH_TODOS_ERROR',
13 | SET_TODO_PAGE = 'SET_TODO_PAGE'
14 | }
15 | interface FetchTodoAction {
16 | type: TodoActionTypes.FETCH_TODOS
17 | }
18 | interface FetchTodoSuccessAction {
19 | type: TodoActionTypes.FETCH_TODOS_SUCCESS;
20 | payload: any[];
21 | }
22 | interface FetchTodoErrorAction {
23 | type: TodoActionTypes.FETCH_TODOS_ERROR;
24 | payload: string;
25 | }
26 | interface SetTodoPage {
27 | type: TodoActionTypes.SET_TODO_PAGE;
28 | payload: number;
29 | }
30 |
31 | export type TodoAction =
32 | FetchTodoAction
33 | | FetchTodoErrorAction
34 | | FetchTodoSuccessAction
35 | | SetTodoPage
36 |
--------------------------------------------------------------------------------
/src/types/user.ts:
--------------------------------------------------------------------------------
1 | export interface UserState {
2 | users: any[];
3 | loading: boolean;
4 | error: null | string;
5 | }
6 | export enum UserActionTypes {
7 | FETCH_USERS = 'FETCH_USERS',
8 | FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS',
9 | FETCH_USERS_ERROR = 'FETCH_USERS_FETCH_USERS_ERROR',
10 | }
11 | interface FetchUsersAction {
12 | type: UserActionTypes.FETCH_USERS;
13 | }
14 | interface FetchUsersSuccessAction {
15 | type: UserActionTypes.FETCH_USERS_SUCCESS;
16 | payload: any[]
17 | }
18 | interface FetchUsersErrorAction {
19 | type: UserActionTypes.FETCH_USERS_ERROR;
20 | payload: string;
21 | }
22 | export type UserAction = FetchUsersAction | FetchUsersErrorAction | FetchUsersSuccessAction
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------