Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ed6b540
4.1 컴포넌트 스타일 지정
HyunSooBae Jan 12, 2023
a3ce879
4.5 비제어 컴포넌트
HyunSooBae Jan 13, 2023
8f8e58b
4.5 제어 컴포넌트와 비제어 컴포넌트
HyunSooBae Jan 13, 2023
b554782
Merge branch 'main' of https://github.com/HyunSooBae/QUICKSTART
HyunSooBae Jan 13, 2023
305cdee
4.9 TodoList 예제
HyunSooBae Jan 15, 2023
19daca9
5장 리액트 클래스 컴포넌트 - ~생명주기 메서드
HyunSooBae Jan 21, 2023
1da441c
5장 리액트 클래스 컴포넌트 예제 따라하기
HyunSooBae Jan 24, 2023
8d7cd4a
6장 예제 작성중...
HyunSooBae Jan 29, 2023
51abb4f
6장 리액트 훅 예제 따라하기
HyunSooBae Jan 30, 2023
4a6d8f1
7장 고차함수와 렌더링 최적화
HyunSooBae Jan 31, 2023
5ecd8ea
7장 고차함수와 렌더링 최적화
HyunSooBae Jan 31, 2023
8630ead
8장 Context API
HyunSooBae Feb 3, 2023
4570b31
9장 ~9.5 리액트 라우터
HyunSooBae Feb 5, 2023
c74642d
Delete dist directory
HyunSooBae Feb 6, 2023
00aa49f
test commit
HyunSooBae Feb 6, 2023
134ff48
test commit2
HyunSooBae Feb 6, 2023
c96c31d
9장 (~9.6) 리액트 라우터가 제공하는 훅
HyunSooBae Feb 8, 2023
7b0aca4
Merge branch 'main' of https://github.com/HyunSooBae/QUICKSTART
HyunSooBae Feb 8, 2023
7da5c6f
Delete dist directory
HyunSooBae Feb 8, 2023
9d0aabe
Add untracked file...
HyunSooBae Feb 8, 2023
80c8004
Merge branch 'main' of https://github.com/HyunSooBae/QUICKSTART
HyunSooBae Feb 8, 2023
1f67926
9장 예제 작성 중...
HyunSooBae Feb 8, 2023
2ea97ce
9장 (~9.8) 리액트 라우터
HyunSooBae Feb 8, 2023
bc2efa3
Modify Loading component
HyunSooBae Feb 8, 2023
022d727
10장 라우팅을 적용한 예제 실습
HyunSooBae Feb 10, 2023
0bad565
10장 예제 수정
HyunSooBae Feb 10, 2023
3bce34c
11장 예제 연습1
HyunSooBae Feb 13, 2023
abdecfb
11장 axios를 이용한 HTTP 통신
HyunSooBae Feb 14, 2023
24ef1b7
12장 (~12.3) 리덕스를 이용한 상태 관리
HyunSooBae Feb 14, 2023
0f96fbf
12장 (~12.4) 리덕스를 이용한 상태 관리
HyunSooBae Feb 15, 2023
2b5569e
12.5.2 테스트용 미들웨어 적용하기
HyunSooBae Feb 17, 2023
58b63a7
12.5.3 간단한 콘솔 로거 미들웨어
HyunSooBae Feb 17, 2023
1a6ab74
12.6.3 redux-thunk 적용하기
HyunSooBae Feb 17, 2023
4e9e6a9
12.6.4 redux-thunk와 @reduxjs/toolkit 함께 사용하기
HyunSooBae Feb 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pnpm-debug.log*
lerna-debug.log*

node_modules
dist
# dist
dist-ssr
*.local

Expand All @@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
node_modules
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## 리액트 퀵스타트 스터디
28,898 changes: 27,375 additions & 1,523 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 22 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,33 @@
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^1.9.2",
"@types/react-redux": "^7.1.25",
"@types/styled-components": "^5.1.26",
"@types/youtube-player": "^5.5.6",
"axios": "^1.3.2",
"bootstrap": "^5.2.3",
"date-and-time": "^2.4.2",
"immer": "^9.0.17",
"p-min-delay": "^4.0.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router": "^6.8.0",
"react-router-dom": "^6.8.0",
"react-spinners": "^0.13.8",
"react-youtube": "^10.1.0",
"redux": "^4.2.1",
"styled-component": "^2.8.0",
"styled-components": "^5.3.6"
},
"devDependencies": {
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^3.0.0",
"typescript": "^4.9.3",
"vite": "^4.0.0"
"vite": "^4.0.0",
"vite-plugin-webpackchunkname": "^0.2.4"
}
}
}
1 change: 1 addition & 0 deletions public/_redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* /index.html 200
Binary file added public/photos/JPike.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/King.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/Mag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/Sam.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/Tim.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/Toby.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'
// import './App.css'

function App() {
const [count, setCount] = useState(0)
Expand Down
36 changes: 36 additions & 0 deletions src/Chapter10/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Layout from './components/Layout'
import Home from './pages/Home'
import About from './pages/About'
import TodoList from './pages/TodoList'
import AddTodo from './pages/AddTodo'
import EditTodo from './pages/EditTodo'
import NotFound from './pages/NotFound'

// import { CallbacksType, StatesType } from "./AppContainer";
// import Loading from "./components/Loading";

// type PropsType = {
// states: StatesType;
// callbacks: CallbacksType;
// }

const App = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="todos" element={<TodoList />} />
<Route path="todos/add" element={<AddTodo />} />
<Route path="todos/edit/:id" element={<EditTodo />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
{/* {states.isLoading ? <Loading /> : ''} */}
</Router>
)
}

export default App
147 changes: 147 additions & 0 deletions src/Chapter10/AppContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { useEffect, useState } from "react"
import App from "./App"
import produce from 'immer'
import axios from 'axios'
// import { string } from "prop-types";

export type TodoItemType = { id: number; todo: string; desc: string; done: boolean }
export type StatesType = { todoList: Array<TodoItemType>; isLoading: boolean }
export type CallbacksType = {
fetchTodoList: () => void;
addTodo: (todo: string, desc: string, callback: () => void) => void;
deleteTodo: (id: number) => void;
toggleDone: (id: number) => void;
updateTodo: (id: number, todo: string, desc: string, done: boolean, callback: () => void) => void;
}

// 다른 사용자 명을 사용하려면 변경할 것
// --> http://localhost:8000/todolist/[user명]/create
const USER = 'hs';
// const BASEURI = '/api/todolist/' + USER;
const BASEURI = '/api/todolist_long/' + USER;

const AppContainer = () => {
let [todoList, setTodoList] = useState<Array<TodoItemType>>([
// {id: 1, todo: '투두1', desc: '투두1 설명', done: false},
// {id: 2, todo: '투두2', desc: '투두2 설명', done: true},
// {id: 3, todo: '투두3', desc: '투두3 설명', done: false},
// {id: 4, todo: '투두4', desc: '투두4 설명', done: false},
])
let [isLoading, setIsLoading] = useState<boolean>(false)

useEffect(() => {
fetchTodoList();
}, []);

// 할 일 목록 조회하는 함수
const fetchTodoList = async () => {
setTodoList([]);
setIsLoading(true);
try {
const res = await axios.get(BASEURI);
setTodoList(res.data)
} catch (err) {
if ( err instanceof Error ) alert('조회 실패 : ' + err.message);
else alert('조회 실패 : ' + err);
}
setIsLoading(false);
}

// 할 일 추가하는 함수
// 할 일 추가가 성공하면 마지막 인자로 전달된 callback을 호출함
const addTodo = async (todo: string, desc: string, callback: () => void) => {
setIsLoading(true);
try {
const res = await axios.post(BASEURI, { todo, desc });
if (res.data.status === 'success') {
// 한 건의 할 일 추가가 성공하면 전체 할 일 목록을 다시 조회하는 것이 아니라
// 추가된 한 건의 정보만 state에 추가함
let newTodoList = produce(todoList, (draft) => {
draft.push({ id: new Date().getTime(), todo, desc, done: false})
});
setTodoList(newTodoList)
callback();
} else {
alert('할 일 추가 실패 : ' + res.data.message);
}
} catch (err) {
if ( err instanceof Error ) console.log('할 일 추가 실패 : ' + err.message);
else alert('할 일 추가 실패 : ' + err);
}
setIsLoading(false);
}

// 할 일 한 건을 삭제하는 함수
const deleteTodo = async (id: number) => {
setIsLoading(true);
try {
const res = await axios.delete(`${BASEURI}/${id}`);
if (res.data.status === 'success') {
let index = todoList.findIndex((todo) => todo.id === id)
let newTodoList = produce(todoList, (draft) => {
draft.splice(index, 1)
})
setTodoList(newTodoList)
} else {
alert('할 일 삭제 실패 : ' + res.data.message);
}
} catch (err) {
if ( err instanceof Error ) alert('할 일 삭제 실패 : ' + err.message);
else alert('할 일 삭제 실패 : ' + err)
}
setIsLoading(false);
}

// 할 일 완료 여부를 토글하는 함수
const toggleDone = async (id: number) => {
setIsLoading(true);
try {
let todoItem = todoList.find((todo) => todo.id === id);
const res = await axios.put(`${BASEURI}/${id}`, {...todoItem, done: !todoItem?.done })
if (res.data.status === 'success') {
let index = todoList.findIndex((todo) => todo.id === id)
let newTodoList = produce(todoList, (draft) => {
draft[index].done = !draft[index].done
})
setTodoList(newTodoList)
} else {
alert('완료 토글 실패 : ' + res.data.message)
}
} catch (err) {
if (err instanceof Error) alert('완료 토글 실패 : ' + err.message);
else alert('완료 토글 실패 : ' + err)
}
setIsLoading(false);
}

// 할 일 수정하는 함수
// 할 일 수정이 성공하면 마지막 인자로 전달된 callback 함수를 호출함
const updateTodo = async ( id: number, todo: string, desc: string, done: boolean, callback: () => void ) => {
setIsLoading(true);
try {
const res = await axios.put(`${BASEURI}/${id}`, { todo, desc, done });
if (res.data.success === 'success') {
let index = todoList.findIndex((todo) => todo.id === id)
let newTodoList = produce(todoList, (draft) => {
draft[index] = { ...draft[index], todo, desc, done }
})
setTodoList(newTodoList);
callback();
} else {
alert('할 일 수정 실패 : ' + res.data.message);
}
} catch (err) {
if (err instanceof Error) alert('할 일 수정 실패 : ' + err.message);
else alert('할 일 수정 실패 : ' + err);
}
setIsLoading(false);
}

// 상태와 액션을 states, callbacks 객체로 묶어서 한꺼번에 속성 전달!!
const callbacks: CallbacksType = { fetchTodoList, addTodo, deleteTodo, toggleDone, updateTodo}
const states: StatesType = { todoList, isLoading }

return <App callbacks={callbacks} states={states} />
}

export default AppContainer
40 changes: 40 additions & 0 deletions src/Chapter10/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useState } from "react";
import { Link } from "react-router-dom";

const Header = () => {
let [isNavShow, setIsNavShow] = useState<boolean>(false)
return (
<nav className="navbar navbar-expand-sm bg-dark navbar-dark">
<span className="navbar-brand ps-2">TodoList App</span>
<button className="navbar-toggler" type="button"
onClick={() => setIsNavShow(!isNavShow)}>
<span className="navbar-toggler-icon"></span>
</button>
<div className={
isNavShow ?
'collapse navbar-collapse show' :
'collapse navbar-collapse'
}>
<ul className="navbar-nav">
<li className="nav-link">
<Link className="nav-link" to='/'>
Home
</Link>
</li>
<li className="nav-link">
<Link className="nav-link" to='/about'>
About
</Link>
</li>
<li className="nav-link">
<Link className="nav-link" to='/todos'>
TodoList
</Link>
</li>
</ul>
</div>
</nav>
)
}

export default Header
13 changes: 13 additions & 0 deletions src/Chapter10/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Outlet } from "react-router"
import Header from "./Header"

const Layout = () => {
return (
<div className="container">
<Header />
<Outlet />
</div>
)
}

export default Layout
17 changes: 17 additions & 0 deletions src/Chapter10/components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ScaleLoader } from "react-spinners"

const Loading = () => {
return (
<div className="bg-white w-100 h-100 position-fixed"
style={{ top: 0, left: 0, opacity: 0.8}}>
<div className="row w-100 h-100 justify-content-center align-items-center">
<div className="col-6 text-center">
<h3>처리 중</h3>
<ScaleLoader height='40px' width='6px' radius='2px' margin='2px' />
</div>
</div>
</div>
)
}

export default Loading
23 changes: 23 additions & 0 deletions src/Chapter10/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
.title {
text-align: center;
font-weight: bold;
font-size: 20pt;
}
.todo-done {
text-decoration: line-through;
}
.container {
padding: 10px;
}
.panel-borderless {
border: 0;
box-shadow: none;
}
.pointer {
cursor: pointer;
}
9 changes: 9 additions & 0 deletions src/Chapter10/pages/About.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const About = () => {
return (
<div className="card card-body">
<h2>About</h2>
</div>
)
}

export default About
Loading