├── .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 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | --------------------------------------------------------------------------------