그동안 배웠던 리액트에 대해 복습한 내용입니다!
1. 리액트와 컴포넌트
2. 리액트와 HTTP 동작
3. 리액트 라우트(loader, action, 기타)
npx create-react-app .
// components/Post.jsx
export default function Post() {
return (
<div>
<p>Zoe</p>
<p>react.js is awesome!</p>
</div>
);
}
// App.jsx
import Post from "./components/Post";
function App() {
return (
<>
<h1>Hello World!</h1>
<Post />
</>
);
}
export default App;
// components/Post.jsx
const names = ["Zoe", "Fubao"];
export default function Post() {
const chosenName = Math.random() > 0.5 ? names[0] : names[1];
return (
<div>
<p>{chosenName}</p>
<p>react.js is awesome!</p>
</div>
);
}
// components/Post.jsx
import styles from "./Post.module.css";
export default function Post({ author, body }) {
return (
<div className={styles.post}>
<p className={styles.author}>{author}</p>
<p className={styles.text}>{body}</p>
</div>
);
}
// App.jsx
import Post from "./components/Post";
function App() {
return (
<main>
<Post author="Zoe" body="React.js is awesome!" />
<Post author="Fubao" body="Check out the full course" />
</main>
);
}
export default App;
// components/PostList.jsx
import Post from "./Post";
import styles from "./PostList.module.css";
export default function PostList() {
return (
<ul className={styles.posts}>
<Post author="Zoe" body="React.js is awesome!" />
<Post author="Fubao" body="Check out the full course" />
<Post author="Aibao" body="이뽀 이뽀 아이바오" />
</ul>
);
}
// components/NewPost.jsx
import classes from "./NewPost.module.css";
function NewPost({ onChange }) {
return (
<form className={classes.form}>
<p>
<label htmlFor="body">Text</label>
<textarea id="body" required rows={3} onChange={onChange} />
</p>
<p>
<label htmlFor="name">Your name</label>
<input type="text" id="name" required />
</p>
</form>
);
}
export default NewPost;
// components/Modal.jsx
import styles from "./Modal.module.css";
export default function Modal({ children, onClose }) {
return (
<>
<div className={styles.backdrop} onClick={onClose} />
<dialog open className={styles.modal}>
{children}
</dialog>
</>
);
}
// components/PostList.jsx
import Post from "./Post";
import styles from "./PostList.module.css";
import NewPost from "./NewPost";
import Modal from "./Modal";
import { useState } from "react";
export default function PostList() {
const [enteredBody, setEnteredBody] = useState("");
const [modalVisible, setModalVisible] = useState(true);
function changeBodyHandler(event) {
setEnteredBody(event.target.value);
}
function hideModalHandler() {
setModalVisible(false);
}
return (
<>
{modalVisible && (
<Modal onClose={hideModalHandler} visible={modalVisible}>
<NewPost onChange={changeBodyHandler} />
</Modal>
)}
<ul className={styles.posts}>
<Post author="Zoe" body={enteredBody} />
<Post author="Fubao" body="Check out the full course" />
<Post author="Aibao" body="이뽀 이뽀 아이바오" />
</ul>
</>
);
}
// components/MainHeader.jsx
import { MdPostAdd, MdMessage } from "react-icons/md";
import classes from "./MainHeader.module.css";
function MainHeader({ onCreatePost }) {
return (
<header className={classes.header}>
<h1 className={classes.logo}>
<MdMessage />
React Poster
</h1>
<p>
<button className={classes.button} onClick={onCreatePost}>
<MdPostAdd size={18} />
New Post
</button>
</p>
</header>
);
}
export default MainHeader;
// components/PostList.jsx
import Post from "./Post";
import styles from "./PostList.module.css";
import NewPost from "./NewPost";
import Modal from "./Modal";
import { useState } from "react";
export default function PostList({ isPosting, onHideModal }) {
const [enteredBody, setEnteredBody] = useState("");
function changeBodyHandler(event) {
setEnteredBody(event.target.value);
}
return (
<>
{isPosting && (
<Modal onClose={onHideModal}>
<NewPost onChange={changeBodyHandler} />
</Modal>
)}
<ul className={styles.posts}>
<Post author="Zoe" body={enteredBody} />
<Post author="Fubao" body="Check out the full course" />
<Post author="Aibao" body="이뽀 이뽀 아이바오" />
</ul>
</>
);
}
// App.jsx
import { useState } from "react";
import MainHeader from "./components/MainHeader";
import PostList from "./components/PostList";
function App() {
const [modalVisible, setModalVisible] = useState(false);
function hideModalHandler() {
setModalVisible(false);
}
function showModalHandler() {
setModalVisible(true);
}
return (
<>
<MainHeader onCreatePost={showModalHandler} />
<main>
<PostList isPosting={modalVisible} onHideModal={hideModalHandler} />
</main>
</>
);
}
export default App;
// components/PostList.jsx
import Post from "./Post";
import styles from "./PostList.module.css";
import NewPost from "./NewPost";
import Modal from "./Modal";
export default function PostList({ isPosting, onHideModal }) {
return (
<>
{isPosting && (
<Modal onClose={onHideModal}>
<NewPost onCancle={onHideModal} />
</Modal>
)}
<ul className={styles.posts}>
<Post author="Fubao" body="Check out the full course" />
<Post author="Aibao" body="이뽀 이뽀 아이바오" />
</ul>
</>
);
}
// components/NewPost.jsx
import classes from "./NewPost.module.css";
import { useState } from "react";
function NewPost({ onCancle }) {
const [enteredBody, setEnteredBody] = useState("");
const [enteredAuthor, setEnteredAuthor] = useState("");
function changeBodyHandler(event) {
setEnteredBody(event.target.value);
}
function changeAuthorHandler(event) {
setEnteredAuthor(event.target.value);
}
function submitHanler(event) {
event.preventDefault();
const postData = {
body: enteredBody,
author: enteredAuthor,
};
}
return (
<form className={classes.form} onSubmit={submitHanler}>
<p>
<label htmlFor="body">Text</label>
<textarea id="body" required rows={3} onChange={changeBodyHandler} />
</p>
<p>
<label htmlFor="name">Your name</label>
<input type="text" id="name" required onChange={changeAuthorHandler} />
</p>
<p className={classes.actions}>
<button type="button" onClick={onCancle}>
취소
</button>
<button type="submit">제출</button>
</p>
</form>
);
}
export default NewPost;
// components/PostList.jsx
import Post from "./Post";
import styles from "./PostList.module.css";
import NewPost from "./NewPost";
import Modal from "./Modal";
import { useState } from "react";
export default function PostList({ isPosting, onHideModal }) {
const [posts, setPosts] = useState([]);
function addPostHandler(postData) {
setPosts((prevPosts) => [...prevPosts, postData]);
}
return (
<>
{isPosting && (
<Modal onClose={onHideModal}>
<NewPost onCancle={onHideModal} onAddPost={addPostHandler} />
</Modal>
)}
<ul className={styles.posts}>
{posts.map((post) => (
<Post author={post.author} body={post.body} key={post.body} />
))}
</ul>
</>
);
}
// components/NewPost.jsx
import classes from "./NewPost.module.css";
import { useState } from "react";
function NewPost({ onCancle, onAddPost }) {
const [enteredBody, setEnteredBody] = useState("");
const [enteredAuthor, setEnteredAuthor] = useState("");
function changeBodyHandler(event) {
setEnteredBody(event.target.value);
}
function changeAuthorHandler(event) {
setEnteredAuthor(event.target.value);
}
function submitHanler(event) {
event.preventDefault();
const postData = {
body: enteredBody,
author: enteredAuthor,
};
onAddPost(postData);
onCancle();
}
return (
<form className={classes.form} onSubmit={submitHanler}>
<p>
<label htmlFor="body">Text</label>
<textarea id="body" required rows={3} onChange={changeBodyHandler} />
</p>
<p>
<label htmlFor="name">Your name</label>
<input type="text" id="name" required onChange={changeAuthorHandler} />
</p>
<p className={classes.actions}>
<button type="button" onClick={onCancle}>
취소
</button>
<button type="submit">제출</button>
</p>
</form>
);
}
export default NewPost;
// components/PostList.jsx
import Post from "./Post";
import styles from "./PostList.module.css";
import NewPost from "./NewPost";
import Modal from "./Modal";
import { useState } from "react";
export default function PostList({ isPosting, onHideModal }) {
const [posts, setPosts] = useState([]);
function addPostHandler(postData) {
fetch("http://localhost:8080/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(postData),
});
setPosts((prevPosts) => [...prevPosts, postData]);
}
return (
<>
{isPosting && (
<Modal onClose={onHideModal}>
<NewPost onCancle={onHideModal} onAddPost={addPostHandler} />
</Modal>
)}
{posts.length > 0 && (
<ul className={styles.posts}>
{posts.map((post) => (
<Post author={post.author} body={post.body} key={post.body} />
))}
</ul>
)}
{posts.length === 0 && (
<div style={{ textAlign: "center", color: "white" }}>
<h2>작성된 포스트가 없습니다.</h2>
<p>포스트를 작성해 보세요!</p>
</div>
)}
</>
);
}
// backend/post.json
{
"posts": [
{ "body": "test", "author": "test", "id": "0.0005431767554782141" },
{
"body": "This course is hopefully very helpful to you!",
"author": "Maximilian",
"id": "post-1"
}
]
}
useEffect()
를 사용해서 데이터 가져오기// components/PostList.jsx
import Post from "./Post";
import styles from "./PostList.module.css";
import NewPost from "./NewPost";
import Modal from "./Modal";
import { useEffect, useState } from "react";
export default function PostList({ isPosting, onHideModal }) {
const [posts, setPosts] = useState([]);
useEffect(() => {
async function fetchPosts() {
const response = await fetch("http://localhost:8080/posts");
if (!response.ok) {
console.log("ERROR");
}
const resData = await response.json();
setPosts(resData.posts);
}
fetchPosts();
}, []);
function addPostHandler(postData) {
fetch("http://localhost:8080/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(postData),
});
setPosts((prevPosts) => [...prevPosts, postData]);
}
return (
<>
{isPosting && (
<Modal onClose={onHideModal}>
<NewPost onCancle={onHideModal} onAddPost={addPostHandler} />
</Modal>
)}
{posts.length > 0 && (
<ul className={styles.posts}>
{posts.map((post) => (
<Post author={post.author} body={post.body} key={post.id} />
))}
</ul>
)}
{posts.length === 0 && (
<div style={{ textAlign: "center", color: "white" }}>
<h2>작성된 포스트가 없습니다.</h2>
<p>포스트를 작성해 보세요!</p>
</div>
)}
</>
);
}
// components/PostList.jsx
import Post from "./Post";
import styles from "./PostList.module.css";
import NewPost from "./NewPost";
import Modal from "./Modal";
import { useEffect, useState } from "react";
export default function PostList({ isPosting, onHideModal }) {
const [posts, setPosts] = useState([]);
const [isFetching, setIsFetching] = useState(false);
useEffect(() => {
async function fetchPosts() {
setIsFetching(true);
const response = await fetch("http://localhost:8080/posts");
if (!response.ok) {
console.log("ERROR");
}
const resData = await response.json();
setPosts(resData.posts);
setIsFetching(false);
}
fetchPosts();
}, []);
function addPostHandler(postData) {
fetch("http://localhost:8080/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(postData),
});
setPosts((prevPosts) => [...prevPosts, postData]);
}
return (
<>
{isPosting && (
<Modal onClose={onHideModal}>
<NewPost onCancle={onHideModal} onAddPost={addPostHandler} />
</Modal>
)}
{!isFetching && posts.length > 0 && (
<ul className={styles.posts}>
{posts.map((post) => (
<Post author={post.author} body={post.body} key={post.id} />
))}
</ul>
)}
{!isFetching && posts.length === 0 && (
<div style={{ textAlign: "center", color: "white" }}>
<h2>작성된 포스트가 없습니다.</h2>
<p>포스트를 작성해 보세요!</p>
</div>
)}
{isFetching && (
<div style={{ textAlign: "center", color: "white" }}>
<p>포스트 불러오는 중...</p>
</div>
)}
</>
);
}
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import App from "./App";
import NewPost from "./components/NewPost";
const router = createBrowserRouter([
{ path: "/", element: <App /> },
{ path: "/create-post", element: <NewPost /> },
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import NewPost from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import Posts from "./routes/Posts";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{
path: "/",
element: <Posts />,
children: [{ path: "/create-post", element: <NewPost /> }],
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
loader()
로 데이터 가져오기// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import NewPost from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import Posts, { loader as PostsLoader } from "./routes/Posts";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{
path: "/",
element: <Posts />,
loader: PostsLoader,
children: [{ path: "/create-post", element: <NewPost /> }],
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
// routes/Posts.jsx
import PostList from "../components/PostList";
import { Outlet } from "react-router-dom";
function Posts() {
return (
<>
<Outlet />
<main>
<PostList />
</main>
</>
);
}
export async function loader() {
const response = await fetch("http://localhost:8080/posts");
if (!response.ok) {
console.log("ERROR");
}
const resData = await response.json();
return resData.posts;
}
export default Posts;
// components/PostList.jsx
import { useLoaderData } from "react-router-dom";
import Post from "./Post";
import styles from "./PostList.module.css";
export default function PostList() {
const posts = useLoaderData();
function addPostHandler(postData) {
fetch("http://localhost:8080/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(postData),
});
}
return (
<>
{posts.length > 0 && (
<ul className={styles.posts}>
{posts.map((post) => (
<Post author={post.author} body={post.body} key={post.id} />
))}
</ul>
)}
{posts.length === 0 && (
<div style={{ textAlign: "center", color: "white" }}>
<h2>작성된 포스트가 없습니다.</h2>
<p>포스트를 작성해 보세요!</p>
</div>
)}
</>
);
}
action()
으로 데이터 전송하기// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import NewPost, { action as newPostAction } from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import Posts, { loader as postsLoader } from "./routes/Posts";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{
path: "/",
element: <Posts />,
loader: postsLoader,
children: [
{ path: "/create-post", element: <NewPost />, action: newPostAction },
],
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
// components/PostList.jsx
import { useLoaderData } from "react-router-dom";
import Post from "./Post";
import styles from "./PostList.module.css";
export default function PostList() {
const posts = useLoaderData();
return (
<>
{posts.length > 0 && (
<ul className={styles.posts}>
{posts.map((post) => (
<Post author={post.author} body={post.body} key={post.id} />
))}
</ul>
)}
{posts.length === 0 && (
<div style={{ textAlign: "center", color: "white" }}>
<h2>작성된 포스트가 없습니다.</h2>
<p>포스트를 작성해 보세요!</p>
</div>
)}
</>
);
}
// routes/NewPost.jsx
import classes from "./NewPost.module.css";
import Modal from "../components/Modal";
import { Link, Form, redirect } from "react-router-dom";
function NewPost() {
return (
<Modal>
<Form method="POST" className={classes.form}>
<p>
<label htmlFor="body">Text</label>
<textarea id="body" required name="body" rows={3} />
</p>
<p>
<label htmlFor="name">Your name</label>
<input type="text" id="name" name="author" required />
</p>
<p className={classes.actions}>
<Link type="button" to="/">
취소
</Link>
<button type="submit">제출</button>
</p>
</Form>
</Modal>
);
}
export async function action({ request }) {
const formData = await request.formData();
const postData = Object.fromEntries(formData); // {body:'...', author:'...'}
await fetch("http://localhost:8080/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(postData),
});
return redirect("/");
}
export default NewPost;
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import NewPost, { action as newPostAction } from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import Posts, { loader as postsLoader } from "./routes/Posts";
import PostDetails, { loader as postDetailsLoader } from "./routes/PostDetails";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{
path: "/",
element: <Posts />,
loader: postsLoader,
children: [
{ path: "/create-post", element: <NewPost />, action: newPostAction },
{ path: "/:id", element: <PostDetails />, loader: postDetailsLoader },
],
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
// PostDetails.jsx
import { useLoaderData, Link } from "react-router-dom";
import Modal from "../components/Modal";
import classes from "./PostDetails.module.css";
function PostDetails() {
const post = useLoaderData();
if (!post) {
return (
<Modal>
<main className={classes.details}>
<h1>Could not find post</h1>
<p>Unfortunately, the requested post could not be found.</p>
<p>
<Link to=".." className={classes.btn}>
Okay
</Link>
</p>
</main>
</Modal>
);
}
return (
<Modal>
<main className={classes.details}>
<p className={classes.author}>{post.author}</p>
<p className={classes.text}>{post.body}</p>
</main>
</Modal>
);
}
export default PostDetails;
export async function loader({ params }) {
const id = params.id;
const response = await fetch("http://localhost:8080/posts/" + id);
const resData = await response.json();
return resData.post;
}
// Post.jsx
import styles from "./Post.module.css";
import { Link } from "react-router-dom";
export default function Post({ id, author, body }) {
return (
<li className={styles.post}>
<Link to={id}>
<p className={styles.author}>{author}</p>
<p className={styles.text}>{body}</p>
</Link>
</li>
);
}
// PostList.jsx
import { useLoaderData } from "react-router-dom";
import Post from "./Post";
import styles from "./PostList.module.css";
export default function PostList() {
const posts = useLoaderData();
return (
<>
{posts.length > 0 && (
<ul className={styles.posts}>
{posts.map((post) => (
<Post
id={post.id}
author={post.author}
body={post.body}
key={post.id}
/>
))}
</ul>
)}
{posts.length === 0 && (
<div style={{ textAlign: "center", color: "white" }}>
<h2>작성된 포스트가 없습니다.</h2>
<p>포스트를 작성해 보세요!</p>
</div>
)}
</>
);
}