728x90
코딩앙마 YouTube
- 폴더 구성
- App.js
//App.js
import Header from "./component/Header"
import DayList from "./component/DayList";
import Day from "./component/Day";
import CreateWord from "./component/CreateWord";
import CreateDay from "./component/CreateDay";
import {BrowserRouter, Routes, Route } from 'react-router-dom';
import EmptyPage from "./component/EmptyPage";
export default function App() {
return (
<BrowserRouter>
<div className="App">
<Header />
<Routes>
<Route exact path = "/" element = {<DayList />} />
<Route path = "/day/:day" element = {<Day />} />
<Route path = "/create_word" element = {<CreateWord />} />
<Route path = "/create_day" element = {<CreateDay />} />
<Route path = "*" element = {<EmptyPage />} />
</Routes>
</div>
</BrowserRouter>
);
}
**v6 기준 Router 형식이 위와 같이 변경되었습니다 :) Switch는 이제 사용할 수 없다!
- CreateDay.js
// CreateDay.js
import { useNavigate } from "react-router";
import useFetch from "../hooks/useFetch";
export default function CreatDay() {
const days = useFetch("http://localhost:3001/days");
const history = useNavigate();
// day추가 로직 = 단어 추가 로직에서 가져옴
function addDay(e){
fetch(`http://localhost:3001/days/`, {
method : 'POST',
headers : {
'Content-Type' : 'application/json',
},
body : JSON.stringify({
day : days.length + 1
}),
}).then(res => {
if(res.ok){
alert("생성 완료 >_<!");
// 주소를 푸쉬해주면 해당 페이지로 이동하게 됨
history(`/`);
}
})
}
return (
<div>
<h3>현재 일수 : {days.length}일</h3>
<button onClick={addDay}>Day 추가</button>
</div>
);
}
**v6 기준 useHistory가 아닌 useNavigate를 사용합니다! history.push가 아닌 history만 사용!
- CreateWord.js
//CreateWord
import useFetch from "../hooks/useFetch";
import { useRef , useState} from "react";
import { useNavigate } from "react-router-dom";
export default function CreateWord() {
const days = useFetch("http://localhost:3001/days");
// 생성 완료 후 바로 day?페이지로 이동시키기
const history = useNavigate();
const [isLoading, setIsLoading] = useState(false);
// 단어추가 로직
function onSubmit(e){
e.preventDefault();
if(!isLoading) {
setIsLoading(true);
// current 속성을 이용하면 해당요소에 접근가능
// value는 input에 입력된 값을 얻을 수 있다.
console.log(engRef.current.value);
console.log(korRef.current.value);
console.log(dayRef.current.value);
fetch(`http://localhost:3001/words/`, {
method : 'POST',
headers : {
'Content-Type' : 'application/json',
},
body : JSON.stringify({
eng : engRef.current.value,
kor : korRef.current.value,
day : dayRef.current.value,
isDone : false
}),
}).then(res => {
if(res.ok){
alert("생성 완료 >_<!");
// 주소를 푸쉬해주면 해당 페이지로 이동하게 됨
history(`/day/${dayRef.current.value}`);
setIsLoading(false);
}
})
}
}
// useRef는 돔에 접근할 수 있게 함. 스크롤 위치 확인하거나 포커스를 주거나 할 때 사용가능
const engRef = useRef(null);
const korRef = useRef(null);
const dayRef = useRef(null);
return (
<form onSubmit={onSubmit}>
<div className="input_area">
<label>Eng</label>
<input type = "text" placeholder="computer" ref={engRef}/>
</div>
<div className="input_area">
<label>Kor</label>
<input type="text" placeholder="컴퓨터" ref={korRef}/>
</div>
<div className="input_area">
<label>Day</label>
<select ref={dayRef}>
{days.map(day => (
<option key = {day.id} value = {day.day}>
{day.day}
</option>
))}
</select>
</div>
<button
style = {{
opacity : isLoading ? 0.3 : 1,
}}>{isLoading ? "Saving..." : "저장"}</button>
</form>
)
}
- Day.js
//Day.js
import { useParams } from "react-router-dom";
import Word from "./Word";
import { useEffect, useState } from "react";
import useFetch from "../hooks/useFetch";
export default function Day() {
const {day} = useParams();
const words = useFetch(`http://localhost:3001/words?day=${day}`);
//const [words, setWords] = useState([]);
// useEffect(() => {
// fetch(`http://localhost:3001/words?day=${day}`)
// .then(res => {
// return res.json();
// })
// .then(data => {
// setWords(data);
// });
// }, [day]);
// string > number 형변환 안됨 죠낸 어이없음;;;;;;;
//const wordList = dummy.words.filter(word => word.day === Number(day));
return (
<>
<h2>Day {day}</h2>
{words.length === 0 && <span>Loading...</span>}
<table>
<tbody>
{words.map(word => (
<Word word = {word} key = {word.id} />
))}
</tbody>
</table>
</>
);
}
- DayList.js
//DayList.js
import { useState , useEffect } from 'react';
import { Link } from "react-router-dom";
import useFetch from '../hooks/useFetch';
export default function DayList() {
const days = useFetch('http://localhost:3001/days')
if(days.length === 0){
return <span>Loading...</span>
}
return (
<>
<ul className='list_day'>
{days.map(day => (
<li key= {day.id}>
<Link to = {`/day/${day.day}`}>
Day {day.day}
</Link>
</li>
))}
</ul>
</>
)
}
< useFetch 사용 전 코드 >
api에서 리스트를 가져와서 바꿔주는 방식, 데이터가 바뀌면 자동으로 렌더링됨
const [days, setDays] = useState([]);
// useEffect : 어떤 상태값이 바뀌었을 때 동작하는 함수를 작성할 수 있음
// 콘솔 찍으면 렌더링 결과가 실제 돔에 반영된 직후이다. 모습이 다 그려지고 나서 찍힌 것임
useEffect(() => {
// 비동기 통신을 위해 fetch 를 사용함
fetch('http://localhost:3001/days')
// then을 이용해서 리턴해줌 res : 는 http응답이고 실제 json은 아니라서 json으로 변환해주는 것임
.then(res => {
return res.json();
})
// data를 받아서 setDays를 해준다.
.then(data => {
setDays(data);
});
console.log("Count change");
// 의존성 배열 : 두번째 매개변수로 배열을 넣고
// [count] count만 눌렀을 때 추가되도록 하려면 뒤에 배열을 넣어주면 됨.
// 만약, 상태값과 무관하게 랜더링 시 한 번만 콘솔찍을 경우는 빈배열을 넣으면된다.
}, []);
- EmptyPage.js
//EmptyPage.js
import { Link } from "react-router-dom";
export default function EmptyPage() {
return (
<div>
<h2>잘못된 접근입니다.</h2>
<Link to = "/">돌아가기</Link>
</div>
)
}
- Header.js
//Header.js
import {Link} from 'react-router-dom';
export default function Header() {
return (
<div className = "header">
<h1>
<Link to ="/">토익 영단어(고급)</Link>
</h1>
<div className="menu">
<Link to = "/create_word" className='link'>
단어 추가
</Link>
<Link to = "/create_day" className='link'>
Day 추가
</Link>
</div>
</div>
)
}
- Word.js
//Word.js
import { useState } from "react";
export default function Word({word: w}) {
const [word, setWord] = useState(w);
const [isShow, setIsShow ] = useState(false);
const [isDone, setIsDone ] = useState(word.isDone);
function toggleShow() {
setIsShow(!isShow)
}
function toggleDone() {
//setIsDone(!isDone)
fetch(`http://localhost:3001/words/${word.id}`, {
method : 'PUT',
headers : {
'Content-Type' : 'application/json',
},
body : JSON.stringify({
...word,
isDone : !isDone
}),
}).then(res => {
if(res.ok){
setIsDone(!isDone);
}
})
}
function del(){
if(window.confirm('삭제 하시겠습니까?')) {
fetch(`http://localhost:3001/words/${word.id}`, {
method : 'DELETE',
}).then (res => {
// 컴포넌트를 다시 렌더링하면 null을 리턴해주면 아무것도 표현해주지 않는다.?
if(res.ok) {
setWord({id:0});
}
})
}
}
if(word.id === 0) {
return null;
}
return (
<tr className={isDone ? "off" : ""}>
<td><input type="checkbox" checked = {isDone} onChange = {toggleDone}/></td>
<td>{word.eng}</td>
{/* // isShow일 때는 */}
<td>{isShow && word.kor}</td>
<td>
<button onClick = {toggleShow}> 뜻 {isShow ? "숨기기" : "보기" }</button>
<button className = "btn_del" onClick={del}>삭제</button>
</td>
</tr>
)
}
- data.json
//data.json
{
"days": [
{
"id": 1,
"day": 1
},
{
"id": 2,
"day": 2
},
{
"id": 3,
"day": 3
},
{
"id": 4,
"day": 4
},
{
"day": 5,
"id": 5
}
],
"words": [
{
"id": 1,
"day": 1,
"eng": "book",
"kor": "책",
"isDone": false
},
{
"id": 3,
"day": 2,
"eng": "car",
"kor": "자동차",
"isDone": false
},
{
"id": 4,
"day": 2,
"eng": "pen",
"kor": "펜",
"isDone": false
},
{
"id": 5,
"day": 3,
"eng": "school",
"kor": "학교",
"isDone": false
},
{
"id": 6,
"day": 3,
"eng": "pencil",
"kor": "연필",
"isDone": false
},
{
"day": "3",
"eng": "window",
"kor": "창문",
"isDone": false,
"id": 7
},
{
"day": "3",
"eng": "house",
"kor": "집",
"isDone": false,
"id": 8
},
{
"day": "2",
"eng": "mouse",
"kor": "쥐",
"isDone": false,
"id": 9
},
{
"day": "4",
"eng": "monkey",
"kor": "원숭이",
"isDone": false,
"id": 10
},
{
"day": "4",
"eng": "apple",
"kor": "사과",
"isDone": false,
"id": 11
},
{
"eng": "dorothy",
"kor": "도로시",
"day": "1",
"isDone": false,
"id": 12
},
{
"eng": "window",
"kor": "창문",
"day": "3",
"isDone": false,
"id": 13
},
{
"eng": "test",
"kor": "시험",
"day": "1",
"isDone": false,
"id": 14
},
{
"eng": "",
"kor": "",
"day": "",
"isDone": false,
"id": 15
},
{
"eng": "jiwon",
"kor": "양지",
"day": "1",
"isDone": false,
"id": 16
}
]
}
- useFetch.js
//hooks/useFetch.js
import { useEffect, useState } from "react";
export default function useFetch(url) {
const [data, setData] = useState([]);
useEffect(() => {
fetch(url)
.then(res => {
return res.json();
})
.then(data => {
setData(data);
});
}, [url]);
return data;
}
- index.css
// index.css
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 20px;
}
ol,
ul {
margin: 0;
padding: 0;
list-style: none;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
a {
text-decoration: none;
color: #333;
}
.App {
width: 800px;
margin: 0 auto;
}
.header {
position: relative;
}
.header .menu {
position: absolute;
top: 10px;
right: 0;
}
.header .link {
border: 1px solid #333;
padding: 10px;
margin-left: 10px;
background-color: #efefef;
font-weight: bold;
border-radius: 4px;
}
.list_day {
display: flex;
flex-wrap: wrap;
}
.list_day li {
flex: 20% 0 0;
box-sizing: border-box;
padding: 10px;
}
.list_day a {
display: block;
padding: 20px 0;
font-weight: bold;
color: #fff;
text-align: center;
border-radius: 10px;
background-color: dodgerblue;
}
table {
border-collapse: collapse;
width: 100%;
}
table td {
width: 25%;
height: 70px;
border: 1px solid #ccc;
text-align: center;
font-size: 26px;
}
table td:first-child {
width: 10%;
}
.off td {
background: #eee;
color: #ccc;
}
.btn_del {
margin-left: 10px;
color: #fff;
background-color: firebrick;
}
button {
padding: 10px;
font-weight: bold;
font-size: 18px;
cursor: pointer;
border: 0 none;
border-radius: 6px;
padding: 10px 20px;
color: #fff;
background-color: dodgerblue;
}
.input_area {
margin-bottom: 10px;
}
.input_area label {
display: block;
margin-bottom: 10px;
}
.input_area input {
width: 400px;
height: 40px;
font-size: 20px;
padding: 0 10px;
}
.input_area select {
width: 400px;
height: 40px;
font-size: 20px;
}
- index.js
//index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
728x90
'🤯TIL > React' 카테고리의 다른 글
[한입 크기로 잘라 먹는 리액트] React에서 배열 사용하기 1 - 리스트 렌더링 (조회) (2) | 2023.11.19 |
---|---|
[한입 크기로 잘라 먹는 리액트] React에서 DOM 조작하기 - useRef (0) | 2023.11.17 |
[한입 크기로 잘라 먹는 리액트] React에서 사용자 입력 처리하기 (0) | 2023.11.17 |
[React vs JavaScript] React란? (0) | 2023.11.04 |
[Linux] 리눅스 가상환경 구성하기 (0) | 2023.07.31 |