코딩 노트

React04 - todoList CRUD / 백엔드 연결 본문

React

React04 - todoList CRUD / 백엔드 연결

newbyeol 2023. 11. 2. 14:24

리엑트 CRUD 복습 예제

demo04 project 생성

cmd에 npx create-react-app demo04 --skip-git 해서 프로젝트를 생성한다.

 

bootstrap을 가져오기 위해 cmd에서

cd demo04로 생성한 프로젝트를 경로로 설정한 뒤

npm i bootstrap
npm i bootswatch 

을 해서 설치한다.

 

index.css, App.css에 내용을 다 지운 뒤,

index.js에 구문을 추가한다. (demo03 거를 가져온다.)

import React from 'react';
import ReactDOM from 'react-dom/client';

//link 대신 import를 통해 설치한 라이브러리 CSS를 불러오도록 처리
//- node_modules에 설치한 요소들은 바로 이름을 사용하여 접근 가능
import 'bootstrap/dist/css/bootstrap.min.css';
//이곳에 bootswatch css파일을 불러오는 구문을 작성
import 'bootswatch/dist/lumen/bootstrap.min.css';

import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

demo04 > src > components 폴더 생성 후 js 파일을 만든다.

properties

props => 태그를 창조하는 느낌

Jumbotron이라는 컴포넌트를 만들고 props로 태그를 줘서 다른 컴포넌트에서 부를 수 있다. (어느 화면에서나 나오게)

Jumbotron.js 생성

//컴포넌트 함수의 매개변수에 props를 적으면 전달되는 속성을 읽을 수 있다.
//- 이를 통해 상위 화면에서 전달되는 데이터를 이용한 프로그래밍이 가능
const Jumbotron = (props)=>{
    return (
        <div className="p-4 text-light bg-info rounded">
            <h1>{props.title}</h1>
            <p>{props.content}</p>
        </div>
    );
};

export default Jumbotron;

 

App.js에 구문을 추가한다.

import { useState } from "react";
import { FaXmark } from "react-icons/fa6";
import Jumbotron from "./components/Jumbotron";

function App() {
  const [todoList, setTodoList] = useState([
      {no:1, title:"학원가기", type:"공부"},
      {no:2, title:"영어단어외우기", type:"공부"},
      {no:3, title:"헬스장가기", type:"운동"},
      {no:4, title:"친구만나기", type:"일상"}
  ]);

  return (
    <div className="container-fluid my-5">
      <div className="row">
        <div className="col-md-8 offset-md-2">

          {/* 점보트론을 만들면서 제목과 내용을 전달 */}
          <Jumbotron title="todoList" content="일정 관리 프로그램"/>

          {/* 입력 화면 */}

          {/* 출력 화면 */}
          <div className="row mt-4">
            {todoList.map(todo=>(
            <div className="col-12 fs-4 mb-2">
              <span className="badge bg-primary me-2">
                {todo.type}
              </span>
              {todo.title}
              <FaXmark className="text-danger"/>
            </div>
            ))}
          </div>

        </div>
      </div>
    </div>
  );
}

export default App;

cd demo04 (알맞은 프로젝트 경로로 들어간 후)

npm install react-icons --save (아이콘을 쓰기 위해 라이브러리 설치)

(npm version이 5가 넘으면 --save를 안 써도 된다.) (npm --version 을 치면 버전이 나온다.) 

 

설치 후 임포트를 하고

import Jumbotron from "./components/Jumbotron";

이런 식으로 작성한다.

<FaXmark className="text-danger"/>

연결 시킬 때는 value, onChange, value까지 있어야 편하다.

 

등록, 삭제, 수정까지 구현한 App.js

import { useState } from "react";
import { FaXmark } from "react-icons/fa6";
import { FaRegEdit } from "react-icons/fa";
import { AiOutlinePlus } from "react-icons/ai";
import Jumbotron from "./components/Jumbotron";

function App() {

  //목록을 위한 state (초기 data)
  const [todoList, setTodoList] = useState([
      {no:1, title:"학원가기", type:"공부"},
      {no:2, title:"영어단어외우기", type:"공부"},
      {no:3, title:"헬스장가기", type:"운동"},
      {no:4, title:"친구만나기", type:"일상"}
  ]);

  //1 등록을 위한 state
  const [data, setData] = useState({title:"", type:""}); //사용자가 입력하는 데이터를 state로 만듬, 목록과 같아야함


  //3 수정을 위한 state
  const [editData, setEditData] = useState({title:"", type:""});

  //1 e로 target을 꺼내고 name과 value를 꺼내서 data를 바꾸는 것이다.
  const changeData = (e)=>{
    setData({
      ...data,
      [e.target.name] : e.target.value //[e.target.name]은 필드명이 변수일 때
    });
  };

  //1 입력한 데이터를 추가하는 함수
  const addTodoList = ()=>{
    //data의 내용을 todoList에 추가 후 data를 초기화하는 것이 목표

    //내용 검사 코드 추가 if(마음에 안 들면) return;
    if(data.title.length === 0 || data.type.length === 0) return; //칸이 비어있으면
   
    //백엔드 가면 시퀀스가 대체하는 부분 (번호 추가하는 부분)
    const last = todoList.length-1;
    const no = todoList.length === 0 ? 1 : todoList[last].no + 1;

    setTodoList([
      ...todoList, //todoList에 있는 건 다 유지를 시키고
      {
        ...data,
        no:no //추가적으로 한 개만...
      }
    ]);

    setData({title:"", type:""}); //데이터 초기화
  };

  //2 todo 항목 삭제
  const deleteTodoList = (todo) => {
    const newTodoList =  todoList.filter(t => t.no !== todo.no); //다른 것만 추려라
    setTodoList(newTodoList);
  };

  //3 todo 수정을 위한 선택
  const editTodoList = (todo) => {
    // setEditData(todo); //안 되는 코드(얕은 복사, shallow copy)
    setEditData({...todo}); //가능한 코드(깊은 복사, deep copy)
  };

  //3
  const changeEditData = e=>{
    setEditData({
      ...editData,
      [e.target.name] : e.target.value
    });
  };
 
  return (
    <div className="container-fluid my-5">
      <div className="row">
        <div className="col-md-8 offset-md-2">

          {/* 점보트론을 만들면서 제목과 내용을 전달 */}
          <Jumbotron title="todoList" content="일정 관리 프로그램"/>

          {/* 입력 화면 */}
          <div className="row mt-4">
            <div className="col-6">
             
              <input className="form-control" name="title" value={data.title}
                        onChange={changeData}/>
            </div>
            <div className="col-3">
              <select className="form-select" name="type" value={data.type}
                        onChange={changeData}>
                <option value="">선택하세요.</option>
                <option>일상</option>
                <option>약속</option>
                <option>취미</option>
                <option>공부</option>
                <option>운동</option>
              </select>
            </div>
            <div className="col-3">
              <button className="btn btn-success" onClick={addTodoList}> {/* e를 써도 전달할 내용이 없음 */}
                <AiOutlinePlus/>
                추가
              </button>
            </div>
          </div>

          <hr/>

          {/* 수정 화면 */}
          <div className="row mt-4">
            <div className="col-6">
              <input type="text" name="title" value={editData.title} onChange={changeEditData}/>
            </div>
            <div className="col-3">
              <select name="type" value={editData.type} onChange={changeEditData}>
                <option>일상</option>
                <option>약속</option>
                <option>취미</option>
                <option>공부</option>
                <option>운동</option>
              </select>
            </div>
          </div>

          {/* 출력 화면 */}
          <div className="row mt-4">
            {todoList.map(todo=>(
            <div className="col-12 fs-4 mb-2">
              <span className="badge bg-primary me-2">
                {todo.type}
              </span>
              {todo.title}

              {/* 수정버튼 */}
              <FaRegEdit className="text-warning ms-1" onClick={e=>editTodoList(todo)}></FaRegEdit>
              {/* 삭제버튼 */}
              <FaXmark className="text-danger" onClick={e=>deleteTodoList(todo)}/>
            </div>
            ))}
          </div>

        </div>
      </div>
    </div>
  );
}

export default App;

괄호 잘 보이는 폰트 : Verdana, Consolas

 

App.js todoList.js CRUD 완성

import { useState, useRef } from "react";
import { FaXmark } from "react-icons/fa6";
import { FaRegEdit } from "react-icons/fa";
import { AiOutlinePlus } from "react-icons/ai";
import Jumbotron from "./components/Jumbotron";
import { Modal } from "bootstrap";

function App() {
  // 목록을 위한 state (초기 data)
  const [todoList, setTodoList] = useState([
    { no: 1, title: "학원가기", type: "공부" },
    { no: 2, title: "영어단어외우기", type: "공부" },
    { no: 3, title: "헬스장가기", type: "운동" },
    { no: 4, title: "친구만나기", type: "일상" },
  ]);

  // 1 등록을 위한 state
  const [data, setData] = useState({ title: "", type: "" }); // 사용자가 입력하는 데이터를 state로 만듬, 목록과 같아야함

  // 3 수정을 위한 state
  const [editData, setEditData] = useState({ title: "", type: "" });

  // 4 모달
  const bsModal = useRef();

  // 1 e로 target을 꺼내고 name과 value를 꺼내서 data를 바꾸는 것이다.
  const changeData = (e) => {
    setData({
      ...data,
      [e.target.name]: e.target.value, // [e.target.name]은 필드명이 변수일 때
    });
  };

  // 1 입력한 데이터를 추가하는 함수
  const addTodoList = () => {
    // data의 내용을 todoList에 추가 후 data를 초기화하는 것이 목표

    // 내용 검사 코드 추가 if(마음에 안 들면) return;
    if (data.title.length === 0 || data.type.length === 0) return; // 칸이 비어있으면

    // 백엔드 가면 시퀀스가 대체하는 부분 (번호 추가하는 부분)
    const last = todoList.length - 1;
    const no = todoList.length === 0 ? 1 : todoList[last].no + 1;

    setTodoList([
      ...todoList, // todoList에 있는 건 다 유지를 시키고
      {
        ...data,
        no: no, // 추가적으로 한 개만...
      },
    ]);

    setData({ title: "", type: "" }); // 데이터 초기화
  };

  // 2 todo 항목 삭제
  const deleteTodoList = (todo) => {
    const newTodoList = todoList.filter((t) => t.no !== todo.no); // 다른 것만 추려라
    setTodoList(newTodoList);
  };

  // 3 todo 수정을 위한 선택
  const editTodoList = (todo) => {
    // setEditData(todo); // 안 되는 코드(얕은 복사, shallow copy)
    setEditData({ ...todo }); // 가능한 코드(깊은 복사, deep copy)

    openModal(); // 모달 열어라
  };

  // 3
  const changeEditData = (e) => {
    setEditData({
      ...editData,
      [e.target.name]: e.target.value,
    });
  };

  // 3 취소 버튼 눌렀을 때
  const clearEditData = () => {
    setEditData({ title: "", type: "" });

    closeModal(); // 모달 닫아라
  };

  // 3 저장 버튼 눌렀을 때
  const saveTodoList = () => {
    // editData의 내용을 todoList에 반영하고 초기화

    const newTodoList = todoList.map((t) => {
      if (t.no === editData.no) {
        return { ...editData }; // 수정했던 데이터를 반환하고
      }
      return t; // 원래 데이터를 반환해라
    });

    setTodoList(newTodoList); // 결과를 반영해라

    clearEditData(); // 바꾸고 초기화해라

    closeModal(); // 모달 닫아라
  };

  // 4 Modal 제어 함수
  const openModal = () => {
    const modal = new Modal(bsModal.current);
    modal.show();
  };

  const closeModal = () => {
    const modal = Modal.getInstance(bsModal.current);
    modal.hide();
  };

  return (
    <div className="container-fluid my-5">
      <div className="row">
        <div className="col-md-8 offset-md-2">
          {/* 점보트론을 만들면서 제목과 내용을 전달 */}
          <Jumbotron title="todoList" content="일정 관리 프로그램" />

          {/* 입력 화면 */}
          <div className="row mt-4">
            <div className="col-6">
              <input
                className="form-control"
                name="title"
                value={data.title}
                onChange={changeData}
              />
            </div>
            <div className="col-3">
              <select
                className="form-select"
                name="type"
                value={data.type}
                onChange={changeData}
              >
                <option value="">선택하세요.</option>
                <option>일상</option>
                <option>약속</option>
                <option>취미</option>
                <option>공부</option>
                <option>운동</option>
              </select>
            </div>
            <div className="col-3">
              <button className="btn btn-success" onClick={addTodoList}>
                <AiOutlinePlus />
                추가
              </button>
            </div>
          </div>

          <hr />

          {/* 출력 화면 */}
          <div className="row mt-4">
            {todoList.map((todo) => (
              <div className="col-12 fs-4 mb-2" key={todo.no}>
                <span className="badge bg-primary me-2">{todo.type}</span>
                {todo.title}
                {/* 수정버튼 */}
                <FaRegEdit
                  className="text-warning ms-1"
                  onClick={() => editTodoList(todo)}
                ></FaRegEdit>
                {/* 삭제버튼 */}
                <FaXmark
                  className="text-danger"
                  onClick={() => deleteTodoList(todo)}
                />
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Modal */}
      <div
        className="modal fade"
        ref={bsModal}
        id="exampleModal"
        tabIndex="-1"
        role="dialog"
        aria-labelledby="exampleModalLabel"
        aria-hidden="true"
        data-bs-backdrop="static"
      >
        <div className="modal-dialog" role="document">
          <div className="modal-content">
            <div className="modal-header">
              <h5 className="modal-title" id="exampleModalLabel">
                일정 변경
              </h5>
              <button
                type="button"
                className="btn btn-danger close"
                data-bs-dismiss="modal"
                aria-label="Close"
              >
                <span aria-hidden="true">&times;</span>
              </button>
            </div>
            <div className="modal-body">
              {/* 수정 화면 */}
              <div className="row">
                <div className="col">
                  <label className="form-label">할일</label>
                  <input
                    type="text"
                    name="title"
                    value={editData.title}
                    className="form-control"
                    onChange={changeEditData}
                  />
                </div>
              </div>
              <div className="row mt-4">
                <div className="col">
                  <label className="form-label">종류</label>
                  <select
                    name="type"
                    value={editData.type}
                    onChange={changeEditData}
                    className="form-select"
                  >
                    <option>일상</option>
                    <option>약속</option>
                    <option>취미</option>
                    <option>공부</option>
                  </select>
                </div>
              </div>
            </div>
            <div className="modal-footer">
              <button className="btn btn-secondary" onClick={clearEditData}>
                취소
              </button>
              <button className="btn btn-success" onClick={saveTodoList}>
                저장
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;