Dorothy_YANG
With Dorothy
Dorothy_YANG
전체 방문자
오늘
어제
  • 분류 전체보기 (279)
    • Hi, I'm Dorothy 🕵️‍♂️ (21)
      • Slowly but Surely (18)
      • IT certifications (3)
    • 🤯TIL (80)
      • HTML & CSS (2)
      • Javascript & jQuery (13)
      • React (13)
      • C언어 (1)
      • JAVA (22)
      • Python (2)
      • Oracle SQL (10)
      • My SQL (5)
      • Spring (12)
    • 💻Programmers (17)
    • 🏫 Open API_JAVA (101)
    • 🌎 Project (10)
      • Shopping (10)
    • 💥 Error (24)
    • ⚙ Setting (23)

블로그 메뉴

  • 홈
  • 방명록

공지사항

인기 글

태그

  • 오류해결
  • oracle
  • 연습문제
  • 콜라보레이토리
  • sql기간
  • SQLD합격후기
  • colaboratory
  • googlecolaboratory
  • 독학후기
  • 시작일종료일
  • java
  • 코딩앙마
  • 비쥬얼스튜디오코드
  • Javascript
  • AllArgsConstructor
  • 기간쿼리
  • CSS
  • Database
  • SQL
  • 백준
  • spring
  • 파이썬온라인
  • Eclipse
  • HTML
  • 이것이자바다
  • 노마드코더
  • 창초기화
  • 기간설정
  • SQLD합격
  • 서버등록

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Dorothy_YANG

With Dorothy

[코딩앙마] 영어 단어장 CRUD_v6기준
🤯TIL/React

[코딩앙마] 영어 단어장 CRUD_v6기준

2023. 7. 31. 20:09
728x90

코딩앙마 YouTube 

https://youtu.be/05uFo_-SGXU

 


  • 폴더 구성


  • 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
    '🤯TIL/React' 카테고리의 다른 글
    • [한입 크기로 잘라 먹는 리액트] React에서 DOM 조작하기 - useRef
    • [한입 크기로 잘라 먹는 리액트] React에서 사용자 입력 처리하기
    • [React vs JavaScript] React란?
    • [Linux] 리눅스 가상환경 구성하기
    Dorothy_YANG
    Dorothy_YANG
    Slowly but Surely, 비전공 문과생의 개발공부

    티스토리툴바