Post

React 프로젝트 생성

React 프로젝트 생성

React로 Frontend 작업

리액트 + spring boot로 프런트와 백엔드를 구성하고 배포까지 하는 과정입니다

방명록을 프로젝트 주제로 생성

 

리액트 시작하기

node js가 설치되어있다면 명령어로 리액트 프로젝트를 생성시킬 수 있다

npx create-react-app my-app 를 루트 폴더에서 실행시킨다

npm start로 리액트 프로젝트가 정상적으로 생성되었는 확인

localhost:3000으로 접속해서 프로젝트가 정상적으로 실행되는지 확인

 

프로젝트 초기 설정

react-router-dom을 이용할거라 프로젝트 위치에서 해당 모듈을 설치한다 npm install react-router-dom

그리고 index.js에 들어가 react-router-dom 모듈을 사용할 수 있도록 설정한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";

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

reportWebVitals();

BrowserRouter를 추가했다

 

 

컴포넌트 생성

이제 프로젝트를 구성할 화면을 작업해야 한다 구성 목록은 4가지로 리스트를 보여줄 페이지, 글을 작성하는 페이지 , 글을 수정하는 페이지(삭제), 디테일 페이지로 구성된다

 

메인 페이지(리스트 뷰어)

메인 페이지에서는 자신의 소개와 사람들이 작성한 방명록을 볼 수 있어야한다

my-app/src 위치에 components 폴더를 만들고 안에 Main.jsx 파일을 생성한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import "./../App.css";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";

function Main() {
  const [posts, setPosts] = useState([]);
  // useState 나중에 비동기 작업을 위함

  return (
    <div className="App-header">
      <h1>안녕하세요 저는 *** 입니다 만나서 반갑습니다</h1>
      <h2>방명록</h2>
      {posts.map((post) => (
        <div key={post.id} className="card">
          <Link to={`/post/${post.id}`}>
            <div className="title">{post.title}</div>
          </Link>
          <p className="writer">작성자: {post.writer}</p>
        </div>
      ))}
      <div className="card">
        <div className="title">예시 방명록 입니다1 </div>
        <div className="writer">test1</div>
      </div>
      <div className="card">
        <div className="title">예시 방명록 입니다2 </div>
        <div className="writer">test2</div>
      </div>
      <Link className="button" to="/write">
        방명록 작성
      </Link>
    </div>
  );
}

export default Main;

아직 방명록 글이 없어서 테스트로 2개를 작성한다

 

 

글 작성 페이지

방명록을 작성하는 페이지로 메인페이지에서 버튼을 누르면 해당 페이지로 이동한다

해당 페이지에서는 글을 작성할 때마다 글이 저장될 변수에 업데이트가 항상 되야한다 따라서 useState를 사용

compenents에 Create.jsx 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import React, { useState } from "react";
import { Link } from "react-router-dom";
import "./create.css";

const PostCreate = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [writer, setWriter] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    // 비동기로 생성 요청을 보내는 코드
  };

  return (
    <div className="App-header">
      <h2>방명록 작성하기</h2>
      <form onSubmit={handleSubmit} className="card">
        <div className="form-group">
          <label className="title">제목</label>
          <input
            type="text"
            id="title"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            required
          />
        </div>
        <div className="form-group">
          <label className="content">내용</label>
          <textarea
            id="content"
            value={content}
            onChange={(e) => setContent(e.target.value)}
            required
          />
        </div>
        <div className="form-group">
          <label htmlFor="writer">작성자</label>
          <input
            type="text"
            id="writer"
            value={writer}
            onChange={(e) => setWriter(e.target.value)}
            required
          />
          <button className="button" type="submit">
            완료
          </button>
        </div>
      </form>
      <Link to={"/"}>돌아가기</Link>
    </div>
  );
};

export default PostCreate;

 

상세 페이지

작성한 방명록을 내용까지 볼 수 있는 페이지

Detail.jsx 파일 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import React, { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import "./../App.css";

function PostDetail() {
  const { postId } = useParams();
  const navigate = useNavigate();
  const [post, setPost] = useState(null);

  const handleHome = () => {
    navigate("/");
  };

  const handleEdit = () => {
    // 수정 페이지로 이동
    navigate(`/edit/${postId}`);
  };

  //삭제 코드
  const handleDelete = () => {};

  // 특정 글을 가져오는 비동기 코드 필요

  if (!post) {
    return <div>Loading...</div>;
  }

  return (
    <div className="App-header">
      <h1>방명록 상세페이지</h1>
      <div className="card">
        <h2 className="title">{post.title}</h2>
        <p>{post.content}</p>
        <p className="writer">작성자: {post.writer}</p>
      </div>
      <div className="button-container">
        <p className="button" onClick={handleEdit}>
          수정
        </p>
        <p className="button" onClick={handleDelete}>
          삭제
        </p>
        <p className="button" onClick={handleHome}>
          돌아가기
        </p>
      </div>
    </div>
  );
}

export default PostDetail;

수정 삭제 버튼을 추가한다

 

 

수정 페이지

Edit.jsx 파일을 생성하고 수정 기능 컴포넌트를 생성한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import React, { useState, useEffect } from 'react';
import {useNavigate, useParams } from 'react-router-dom';
import './edit.css';

const PostEdit = () => {
  const { postId } = useParams();
  const navigate = useNavigate();
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [writer, setWriter] = useState('');
  const [originalPost, setOriginalPost] = useState({});


  // 기존 글 데이터를 불러오는 비동기 함수 호출

  const handleTitleChange = e => {
    setTitle(e.target.value);
  };

  const handleContentChange = e => {
    setContent(e.target.value);
  };

  const handleWriterChange = e => {
    setWriter(e.target.value);
  };

  const handleSubmit = e => {
    e.preventDefault();

    // 수정된 데이터를 서버에 보내는 비동기 함수 호출
  };

  const handleCancel = () => {
    // 수정 취소 후 글 상세 페이지로 이동
    navigate(`/post/${postId}`);
  };

  return (
    <div className="App-header">
      <h2>Edit Post</h2>
      <form onSubmit={handleSubmit} className="card">
        <div className="form-group">
          <label>Title</label>
          <input
            type="text"
            value={title}
            onChange={handleTitleChange}
            required
          />
        </div>
        <div className="form-group">
          <label>Content</label>
          <textarea
            value={content}
            onChange={handleContentChange}
            required
          ></textarea>
        </div>
        <div className="form-group">
          <label>Writer</label>
          <input
            type="text"
            value={writer}
            onChange={handleWriterChange}
            required
          />
        </div>
        <div>
        <div className="button-container">
          <button type="submit">수정 완료</button>
          <button type="button" onClick={handleCancel}>
            돌아가기
          </button>
        </div>
        </div>
      </form>
    </div>
  );
};

export default PostEdit;

 

 

컴포넌트 연결하기

컴포넌트를 다 생성 후 이 컴포넌트를 연결해줘야한다 app.js에서 파일을 수정한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react";
import { Routes, Route } from "react-router-dom";
import Main from "./components/Main";
import Detail from "./components/Detail";
import Create from "./components/Create";
import Edit from "./components/Edit";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Main />} />
      <Route path="/post/:postId" element={<Detail />} />
      <Route path="/write" element={<Create />} />
      <Route path="/edit/:postId" element={<Edit />} />
    </Routes>
  );
}

export default App;

Route를 이용해서 컴포넌트를 보여줄 url을 연결한다

 

 

CSS 파일

App.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.card {
  background-color: #3498db;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 16px;
  color: #fff;
  margin: 10px;
  width: 50%;
}

.title {
  background-color: #87ceeb;
  padding: 8px;
  margin-bottom: 12px;
  font-size: 18px;
  font-weight: bold;
  text-decoration: none;
}

.content {
  margin-bottom: 8px;
}

.writer {
  font-style: italic;
}

.button-container {
  display: flex;
  justify-content: center;
  gap: 10px;
}

.button {
  background-color: #61dafb;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  text-decoration: none;
}

.button:hover {
  background-color: #5ac8e0;
}

.post-create {
  margin-top: 20px;
}

 

create.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
}

.card {
  background-color: #3498db;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 16px;
  margin: 10px;
  width: 400px;
  max-width: 100%;
}

.card-title {
  color: #333333;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 8px;
}

.card-content {
  color: #555555;
  margin-bottom: 8px;
}

label {
  display: block;
  font-weight: bold;
  color: #666666;
}

.form-group {
  margin-bottom: 16px;
}

input[type="text"],
textarea {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

button {
  background-color: #61dafb;
  color: white;
  border: none;
  margin: 5px;
  border-radius: 4px;
  padding: 8px 16px;
  cursor: pointer;
  font-size: 16px;
}

button:hover {
  background-color: #47c5f1;
}

 

edit.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
h2 {
  text-align: center;
}

.card {
  background-color: #3498db;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 16px;
  color: #fff;
  margin: 10px;
  width: 50%;
}

.form-group {
  margin-bottom: 10px;
}

label {
  display: block;
  font-weight: bold;
  margin-bottom: 5px;
}

input[type="text"],
textarea {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.button-container {
  display: flex;
  justify-content: center;
  gap: 10px;
}

.button {
  background-color: #61dafb;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  text-decoration: none;
}

.button:hover {
  background-color: #5ac8e0;
}

button[type="submit"] {
  background-color: #61dafb;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  text-decoration: none;
}

button[type="button"] {
  background-color: #61dafb;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  text-decoration: none;
}

This post is licensed under CC BY 4.0 by the author.