๐ ํ์ฌ๊น์ง ๊ตฌํํ ๋ด์ฉ ์์
๋ถํธ์บ ํ ์์ ์์ ๋ฆฌ์กํธ์ state์ props๋ฅผ ๊ณต๋ถํ๊ฒ ๋๋ฉด์ ํด๋น ๋ด์ฉ๋ค์ ์ฌ์ฉํ๋ ๊ณผ์ ๋ฅผ ๊ตฌํํ๊ฒ ๋์๋ค.
๋๋ ๊ณผ์ ๋ฅผ ํผ์์ ์ฒ์๋ถํฐ ๋๊น์ง ๋ง๋ค์ด๋ณด๋ฉด์ ๊ณต๋ถํ ๊ฒ์ ๋ ์ตํ๊ณ ์ถ์๊ณ ํด๋ณด๊ณ ์ถ์ ์ถ๊ฐ ๊ธฐ๋ฅ๋ค์ ๋ง๋ถ์ฌ์ ๊ตฌํํด๋ณด๊ธฐ๋ก ํ๋ค. ๊ตฌํํ๊ธฐ๋ก ๋ชฉํํ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ๋ค.
๐ ๋ชฉํ
- ์ฌ์ด๋๋ฐ์ ๊ฐ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํด๋นํ๋ ํ์ด์ง๋ก ์ด๋ํ๊ฒ ๋ผ์ฐํฐ ๊ตฌํํ๊ธฐ
- ํธ์ ๋ชฉ๋ก์ด ๋ณด์ด๋ ํ์ด์ง(Tweets.js)์์ ๋๋ฏธ๋ฐ์ดํฐ์ ๋ด์ฉ์ด ์๋ง๊ฒ ๋ณด์ด๊ฒ ํ๊ณ ์ ๋ ฅํผ ๋ง๋ค๊ธฐ
- ์ฌ์ฉ์ ํ์ด์ง(MyPage.js)์๋ (๋ก๊ทธ์ธํ๋ค๊ณ ๊ฐ์ ํ๋)์ ์ ์ ํ๋กํ์ด ๋ณด์ด๊ฒ ํ๊ณ , ๋๋ฏธ๋ฐ์ดํฐ์์ ํด๋น ์ ์ ์ ํธ์๋ง ๋ณด์ด๊ฒ ํ๊ธฐ
- Tweets.js์์ ์ ๋ ฅํผ์ ๋ด์ฉ ์ ๋ ฅํ ์ ์กํ๋ฉด ๋ชฉ๋ก์ ํธ์์ด ์ถ๊ฐ๋๊ณ ํธ์์ ์ต์ ์์ผ๋ก ๋ํ๋๊ฒ ํ๊ธฐ
- ํธ์ ์ญ์ ๊ธฐ๋ฅ ์ถ๊ฐํ๊ธฐ
- ํธ์์ ๋ณธ๋ฌธ์ ํด๋ฆญํ๋ฉด ์์ ๋๋(click-to-edit UI) ๋ฐฉ์์ผ๋ก ํธ์ ์์ ๊ตฌํํ๊ธฐ
- ์๋ก๊ณ ์นจ๋์ด๋ ๊ณ์ ๋ณผ์์๊ฒ ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๊ตฌํํ๊ธฐ
๐ ํด๋ ๊ตฌ์กฐ
โโโ /twittler-self
โ โโโ README.md
โ โโโ /public
โ โโโ /src # React ์ปดํฌ๋ํธ๊ฐ ๋ค์ด๊ฐ๋ ํด๋
โ โโโ static # dummyData๊ฐ ๋ค์ด๊ฐ๋ ํด๋
โ โ โโโ dummyData.js
โ โโโ Pages # ํ์ด์ง๋ฅผ ํ์ํ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค์ด๊ฐ๋ ํด๋
โ โ โโโ About.js
โ โ โโโ Mypage.js
โ โ โโโ Tweets.js
โ โโโ Components # ๋จ์ผ ์ปดํฌ๋ํธ๊ฐ ๋ค์ด๊ฐ๋ ํด๋
โ โ โโโ Tweet.js
โ โโโ App.css
โ โโโ App.js
โ โโโ Footer.js
โ โโโ index.js
โ โโโ Sidebar.js
โ package.json
โ .gitignore
๐งฑ ํ์ด์ง ๋ ์ด์์
์ ์ฒด์ ์ธ ๋ ์ด์์์ ์ด๋ ๊ฒ ์์ด์ด ํ๋ ์์ ์ง๋ณด์๋ค.
์ผ์ชฝ์๋ ์ฌ์ดํธ๋ฐ์ ๊ฐ ํ์ด์ง๋ก ์ด๋ํ ๋ฒํผ๋ค์ด ๋ค์ด๊ฐ๊ณ
์ค๋ฅธ์ชฝ์๋ ํ๋กํ ์์ญ/ํธ์์ ๋ ฅํผ์ด ๋ ์๋จ ๋ถ๋ถ๊ณผ ํธ์ ๋ฆฌ์คํธ๊ฐ ์ถ๋ ฅ๋ ํ๋จ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑํ๋ค.
๊ฐ๊ฐ์ ํธ์์ ์ด๋ฏธ์ง์ ์ ๋ณด, ํ ์คํธ ์์ญ์ผ๋ก ๊ตฌ์ฑ๋๊ณ ํธ์ ์ ๋ ฅํผ์ ๊ฒฝ์ฐ ์ ์ก๋ฒํผ๊ณผ ๋ก์ปฌ์คํ ๋ฆฌ์ง ์ญ์ ๋ฒํผ์ ์ถ๊ฐํ๊ธฐ๋ก ํ๋ค.
๐พ ๋๋ฏธ ๋ฐ์ดํฐ
{
id: 2,
username: "harry00700",
picture: `https://randomuser.me/api/portraits/women/50.jpg`,
content:
"Proin leo odio, porttitor id, .....",
createdAt: "2022-02-25T16:17:47.000Z",
updatedAt: "2022-02-25T16:17:47.000Z",
},
๋๋ฏธ ๋ฐ์ดํฐ๋ ๊ณผ์ ์ ๋น์ทํ ํํ๋ก ์์ฑํ๊ณ , ๋ค์ด๊ฐ๋ ๋ด์ฉ์ ๋ณ๊ฒฝํด์ฃผ์๋ค.
๋๋ฏธ ๋ฐ์ดํฐ๋ https://www.mockaroo.com/ < ์ด ์ฌ์ดํธ๋ฅผ ์ด์ฉํด์ ๋ง๋ค์๋ค. ์๊ฐ๋ณด๋ค ์ฌ์ฉํ๊ธฐ ์ฌ์ ๋ค.
๐ ๊ตฌํํ ์ฝ๋
1. ๋ผ์ฐํฐ ๊ตฌํ
src/App.js
import { Routes, Route } from "react-router-dom";
import "./App.css";
import Sidebar from "./Sidebar";
import Tweets from "./Pages/Tweets";
import About from "./Pages/About";
import MyPage from "./Pages/MyPage";
function App() {
return (
<div className="App">
<Sidebar />
<section className="features">
<Routes>
<Route path="/" element={<Tweets />} />
<Route path="/about" element={<About />} />
<Route path="/mypage" element={<MyPage />} />
</Routes>
</section>
</div>
);
}
export default App;
๋ผ์ฐํ ๋๋ ์ฃผ์์ ๋ฐ๋ผ features ์ปดํฌ๋ํธ ๋ด๋ถ์ ๊ฐ๊ฐ ๋ค๋ฅธ ํ์ด์ง ์ปดํฌ๋ํธ๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ๋ก ํ๋ค.
ํธ์๋ค์ ๋ณด์ฌ์ฃผ๋ ์ปดํฌ๋ํธ์ธ <Tweets>๊ฐ ๋ฉ์ธ ์ปดํฌ๋ํธ์ด๋ค.
src/Sidebar.js
import { Link } from "react-router-dom";
const Sidebar = () => {
return (
<section className="Sidebar">
<Link to="/">
<div>tweets</div>
</Link>
<Link to="/about">
<div>about</div>
</Link>
<Link to="/mypage">
<div>mypage</div>
</Link>
</section>
);
};
export default Sidebar;
<Sidebar> ์ปดํฌ๋ํธ์์ Link ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํด ๊ฐ div๋ฅผ ํด๋ฆญํ๋ฉด to ์์ฑ์ ํตํด Route ์ปดํฌ๋ํธ์ ์ค์ ํ path ์ฃผ์์ ์ฐ๊ฒฐํด์ฃผ์๋ค.
Link ์ปดํฌ๋ํธ๋ ๋ ๋๋ง๋ ๋ a ์์๋ก ๋ณ๊ฒฝ๋๊ธฐ๋๋ฌธ์ button ์์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค div๊ฐ ๋์๊ฒ๊ฐ์์ ์ ๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋๋ฐ, ์๊ฐํด๋ณด๋ ์นํ์ค์ ์๋ฐฐ๋ ์๋ ์๋ค๋ ์๊ฐ์ด ๋ ๋ค.
=> ๊ด์ฐฎ๋ค! HTML5์์๋ aํ๊ทธ๊ฐ div์์๋ฅผ ๊ฐ์ธ๋๋ก ํ์ฉํ๊ธฐ ๋๋ฌธ์ ์น์ ๊ทผ์ฑ์ ์๋ฐฐ๋์ง ์๋๋ค. ๋จ ์ด๊ฒ์ aํ๊ทธ๋ง ๊ฐ๋ฅํด์ง๊ฒ์ด๊ณ ๊ธฐํ ์ธ๋ผ์ธ ์์๋ค์ ๋ด๋ถ์๋ ๋ธ๋ญ์์๋ฅผ ๋ฃ์ ์ ์๋ค.
Although previous versions of HTML restricted the a element to only containing phrasing content (essentially, what was in previous versions referred to as “inline” content), the a element is now transparent; that is, an instance of the a element is now allowed to also contain flow content (essentially, what was in previous versions referred to as “block” content)—if the parent element of that instance of the a element is an element that is allowed to contain
flow content.
http://w3c.github.io/html-reference/a.html#a-changes
2. Tweets ์ปดํฌ๋ํธ์ ๋๋ฏธ๋ฐ์ดํฐ ํธ์ ์ถ๋ ฅํ๊ธฐ
๐ป ๊ตฌํ ํ๋ฉด
<Tweets> ์ปดํฌ๋ํธ์์ ๋๋ฏธ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ map ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๋ชจ๋ ํธ์์ ๋ ๋๋งํ๊ฒ ํ ๊ฒ์ด๋ค.
์ด ๊ตฌ์กฐ๋ฅผ ์๊ฐํ๋ฉด์ ๋จผ์ src/Components/Tweet.js ํ์ผ์ ๊ฐ๊ฐ์ ๊ฐ๋ณ ํธ์์ด ๋ <Tweet> ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ๋ค.
src/Components/Tweet.js
const Tweet = ({ tweet }) => {
const parseDate = (createdAt) =>
new Date(createdAt).toLocaleDateString("ko-KR");
return (
<li>
<div className="tweet-avatar">
<img src={tweet.picture} />
</div>
<div className="tweet-content">
<div className="tweet-info">
<span className="tweet-username">{tweet.username}</span>
<span className="tweet-date">{parseDate(tweet.createdAt)}</span>
<button>delete</button>
</div>
<div className="tweet-msg">
{tweet.content}
</div>
</div>
</li>
);
};
export default Tweet;
src/Pages/Tweets.js
import { useState } from "react";
import Tweet from "../Components/Tweet";
import dummyTweets from "../static/dummyData";
const Tweets = () => {
const [tweetList, setTweetList] = useState(dummyTweets);
return (
<div className="tweets">
<div className="tweet-form">
<div className="tweet-avatar">
<img src="https://randomuser.me/api/portraits/women/50.jpg" />
</div>
<div className="tweet-input-wrapper">
<div>
<input type="text" value="harry00700" />
<textarea></textarea>
</div>
<div className="tweet-buttons">
<button type="button">
send
</button>
</div>
</div>
</div>
<ul className="tweet-list">
{tweetList.map((tweet) => {
return (
<Tweet
tweet={tweet}
key={tweet.id}
/>
);
})}
</ul>
</div>
);
};
export default Tweets;
useState๋ฅผ ์ฌ์ฉํด tweetList๋ผ๋ state๋ฅผ ๋ง๋ค๊ณ ์ด๊ธฐ๊ฐ์ static/dummyData.js์ dummyTweets๋ก ๋ฃ์ด์ค๋ค.
๊ทธ๋ฆฌ๊ณ .tweet-list๋ด๋ถ์ map ๋ฉ์๋๋ฅผ ์ฌ์ฉํด <Tweet> ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ๋ ๋๋ง ๋๊ฒ ํ๋ค.
map ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ๋๋ ๊ฐ๊ฐ์ ์์์ ๊ณ ์ ํ key๊ฐ ํ์ํ๊ธฐ ๋๋ฌธ์ tweet์ id๋ฅผ key๋ก ์ค์ ํ๋ค.
์ฌ๊ธฐ์์ props๋ก ๋๊ฒจ์ค tweet์ด <Tweet> ์ปดํฌ๋ํธ์์ ๊ตฌ์กฐ๋ถํดํ ๋น์ผ๋ก ๊บผ๋ด์ ธ ๊ฐ๊ฐ์ ํธ์ ์ปดํฌ๋ํธ์ ๊ฐ์ด ๋ค์ด๊ฐ๊ฒ ๋๋ค.
3. ํธ์ ์์ฑํ๊ธฐ
๐ป ๊ตฌํ ํ๋ฉด
ํธ์ ์์ฑ์ ๊ตฌํํ ๋ ์๊ฐํ๊ฒ์ ์ด๊ฒ์ด์๋ค.
- send ๋ฒํผ์ด ํด๋ฆญ๋ ๋ ์์ฑํ ํธ์์ด ๋ฐ์ดํฐ์ ์ถ๊ฐ๋๊ฒ ํ๊ธฐ
- ์ถ๊ฐ๋ ํธ์์ ์์์๋ถํฐ ์ฆ, ์ต์ ์์ผ๋ก ๋ํ๋๊ฒ ํ๊ธฐ
- ์์ฑํ ๋ด์ฉ์ด ์๋๋ฐ send ๋ฒํผ์ด ๋๋ฌ์ก์๊ฒฝ์ฐ ๊ฒฝ๊ณ ์ฐฝ์ ๋์ฐ๊ณ ํธ์์ด ์ ๋ ฅ๋์ง ์๊ฒ ํ๊ธฐ
๋ค์ id๋ฅผ ์ง์ ํ๊ธฐ ์ํด useRef ํ ์ ์ฌ์ฉํ๋ค.
useRef๋ ํน์ DOM์ ์ง์ ์ ํํ๊ฑฐ๋ ์ด๋ ํ ๋ณ์๋ฅผ ๋ด์๋๋ ค๊ณ ํ ๋ ์ฌ์ฉํ๋ค.
์ด๋ฒ์๋ ๋ค์ ํธ์์ id๋ฅผ ๋ณ์๋ก ๋ด์๋๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
๊ตฌํํ ์ฝ๋๋ ์๋์ ๊ฐ๋ค.
import { useState, useRef } from "react";
import Tweet from "../Components/Tweet";
import dummyTweets from "../static/dummyData";
const Tweets = () => {
const [tweetList, setTweetList] = useState(dummyTweets); // ์ ์ฒด ํธ์ ๋ฆฌ์คํธ
const [user, setUser] = useState("harry00700"); // ์ ์ ์ด๋ฆ input์ state
const [msg, setMsg] = useState(""); // ํธ์ ์
๋ ฅ textarea์ state
const nextId = useRef(6); // ๋ค์ id ์ง์
const handleUser = (event) => {
setUser(event.target.value);
};
const handleMsg = (event) => {
setMsg(event.target.value);
};
const addTweet = () => {
if (msg === "") { // tweet ๋ด์ฉ์ด ๋น์ด์๋ค๋ฉด
alert("ํธ์ ๋ด์ฉ์ ์ ์ด์ฃผ์ธ์.");
} else {
const tweet = {
id: nextId.current,
username: user,
picture: `https://randomuser.me/api/portraits/women/50.jpg`,
content: msg,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
// tweetList์ ๋งจ ์์ ์๋ก ์์ฑํ ํธ์ ์ถ๊ฐํ๊ณ input๋ค์ ๊ฐ ์ด๊ธฐํ + ๋ค์ id์ค์
setTweetList([tweet, ...tweetList]);
setUser("harry00700");
setMsg("");
nextId.current += 1;
}
};
return (
<div className="tweets">
<div className="tweet-form">
<div className="tweet-avatar">
<img src="https://randomuser.me/api/portraits/women/50.jpg" />
</div>
<div className="tweet-input-wrapper">
<div>
<input type="text" value={user} onChange={handleUser} />
<textarea value={msg} onChange={handleMsg}></textarea>
</div>
<div className="tweet-buttons">
<button type="button" onClick={addTweet}>
send
</button>
</div>
</div>
</div>
<ul className="tweet-list">
{tweetList.map((tweet) => {
return (
<Tweet
tweet={tweet}
key={tweet.id}
/>
);
})}
</ul>
</div>
);
};
export default Tweets;
4. ํธ์ ์ญ์ ํ๊ธฐ
๊ฐ๊ฐ์ ํธ์๋ค์๋ delete ๋ฒํผ์ด ์๋ค. ์ด ๋ฒํผ์ด ํด๋ฆญ๋์์๋ ํด๋น ํธ์์ด ์ญ์ ๋๋ ์ด๋ฒคํธ๋ฅผ state ๋์ด์ฌ๋ฆฌ๊ธฐ๋ฅผ ์ฌ์ฉํด ๊ตฌํํ๋ค.
ํธ์ ์ญ์ ๋ฒํผ์ ํ์ ์ปดํฌ๋ํธ์ธ Tweet.js์ ์์ง๋ง ํธ์ ๋ฆฌ์คํธ์ state๋ ์์ ์ปดํฌ๋ํธ์ธ Tweets.js์ ์๊ธฐ๋๋ฌธ์ ์ด๋ ๊ฒ ๊ตฌํํ์๋ค. ์ญ์ ์ด๋ฒคํธ๋ฅผ ์์ ์ปดํฌ๋ํธ์์ ํ์ ์ปดํฌ๋ํธ๋ก props๋ฅผ ํตํด ๋ด๋ ค์ฃผ๊ณ ํ์์ปดํฌ๋ํธ์์ ์ด๋ฒคํธ๋ฅผ ํธ์ถํ๋ฉฐ ํ์ํ ์ธ์๋ฅผ ๋๊ฒจ์ค๋ค.
Tweet.js
const Tweet = ({ tweet, deleteTweet }) => {
const parseDate = (createdAt) =>
new Date(createdAt).toLocaleDateString("ko-KR");
return (
<li>
<div className="tweet-avatar">
<img src={tweet.picture} />
</div>
<div className="tweet-content">
<div className="tweet-info">
<span className="tweet-username">{tweet.username}</span>
<span className="tweet-date">{parseDate(tweet.createdAt)}</span>
// ๋ฒํผ์ด ํด๋ฆญ๋๋ฉด deleteTweet ์ด๋ฒคํธ ์คํ + ํด๋ฆญ๋ ํธ์์ id๋ฅผ ์ธ์๋ก ๋๊ฒจ์ค
<button onClick={() => deleteTweet(tweet.id)}>delete</button>
</div>
<div className="tweet-msg">
{tweet.content}
</div>
</div>
</li>
);
};
export default Tweet;
Tweets.js
const deleteTweet = (id) => {
// tweet id๊ฐ ์ธ์๋ก ๋๊ฒจ์ง id์ ์ผ์นํ์ง ์๋ ํธ์๋ง ํํฐ๋ง
// = ์ธ์๋ก ๋๊ฒจ์ง id์ ํธ์๋ง ์ ์ธํ๊ณ ์ํ๋ฅผ ๋ณ๊ฒฝํจ = ํธ์ ์ญ์
const deleteList = tweetList.filter((tweet) => tweet.id !== id);
setTweetList(deleteList);
};
5. ํธ์ ์์ ํ๊ธฐ
๐ป ๊ตฌํ ํ๋ฉด
ํธ์์ ๋ณธ๋ฌธ์ ํด๋ฆญํ๋ฉด textarea์ ์์ ๋ฒํผ์ด ๋ํ๋๊ณ , ๋ด์ฉ์ ์์ ํ ํ edit ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํธ์ ๋ด์ฉ์ด ์์ ๋๊ฒ ๊ตฌํํ๋ค.
ํธ์์ ์์ ์ํ๋ฅผ ๋ํ๋ด๊ธฐ ์ํด์ isEdit state๋ฅผ ์์ฑํ๊ณ , ์์ ๋ ํธ์์ ์ํ๋ฅผ ์ ์ฅํ๊ธฐ ์ํด updateMsg state๋ฅผ ์์ฑํ๋ค.
ํธ์์ ๋ณธ๋ฌธ์ ํด๋ฆญํ๋ฉด onEdit ์ด๋ฒคํธ๊ฐ ์คํ๋๋ฉฐ ์์ ์ํ๊ฐ true๋ก ๋ณํ๊ณ ํธ์์ ๋ด์ฉ์ด ์ ๋ ฅ๋์ด์๋ textarea๊ฐ ๋ํ๋๋ค.
์์ ํ edit ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํด๋น ํธ์์ id์ ์์ ๋ ํธ์์ ํ ์คํธ๊ฐ ์ธ์๋ก ์ ๋ฌ๋๊ณ ์์ ์ปดํฌ๋ํธ Tweets.js์์ ์ ๋ฌ๋ updateTweet์ด ์คํ๋๋ค.
Tweet.js
import { useState, useRef } from "react";
const Tweet = ({ tweet, deleteTweet, updateTweet }) => {
const parseDate = (createdAt) =>
new Date(createdAt).toLocaleDateString("ko-KR");
const [isEdit, setIsEdit] = useState(false); // ํธ์ ์์ ์ํ
const [updateMsg, setUpdateMsg] = useState(tweet.content); // ํธ์ ์์ textarea
const textareaRef = useRef();
const onEdit = () => {
setIsEdit(true);
};
const handleUpdateMsg = (event) => {
setUpdateMsg(event.target.value);
textareaRef.current.style.height = textareaRef.current.scrollHeight + "px";
};
const onUpdate = (id, content) => {
setIsEdit(false);
updateTweet(id, content);
};
return (
<li>
<div className="tweet-avatar">
<img src={tweet.picture} />
</div>
<div className="tweet-content">
<div className="tweet-info">
<span className="tweet-username">{tweet.username}</span>
<span className="tweet-date">{parseDate(tweet.createdAt)}</span>
<button onClick={() => deleteTweet(tweet.id)}>delete</button>
</div>
{isEdit ? (
<>
<textarea
className="tweet-msg-edit"
rows="7"
value={updateMsg}
ref={textareaRef}
onChange={handleUpdateMsg}
></textarea>
<button onClick={() => onUpdate(tweet.id, updateMsg)}>edit</button>
</>
) : (
<div className="tweet-msg" onClick={onEdit}>
{tweet.content}
</div>
)}
</div>
</li>
);
};
export default Tweet;
Tweets.js
const updateTweet = (id, content) => {
const updateList = tweetList.map((tweet) =>
tweet.id === id
// ์ธ์๋ก ๋๊ฒจ์ง id์ ๊ฐ์ id๋ฅผ ๊ฐ์ง ํธ์์ ๋ด์ฉ์ ์์
// ๋ค๋ฅธ ๋ด์ฉ์ ๊ทธ๋๋ก ๋๊ณ (...tweet)
// content์ updatedAt๋ง ์์
? { ...tweet, content, updatedAt: new Date().toISOString() }
: tweet
);
setTweetList(updateList);
};
6. ๋ก์ปฌ ์คํ ๋ฆฌ์ง ์ฌ์ฉ
์๋ก๊ณ ์นจํด๋ ํธ์์ด ๊ณ์ ์ ์ฅ๋์ด์๊ฒ ํ๊ธฐ ์ํด ๋ก์ปฌ์คํ ๋ฆฌ์ง๋ฅผ ์ฌ์ฉํ๋ค.
๋งจ ์ฒ์ ์ ์ฅ๋ ๋ก์ปฌ์คํ ๋ฆฌ์ง์ ํธ์์ ๋ถ๋ฌ์ค๊ธฐ ์ํด useEffect ํ ์ ์ฌ์ฉํ๋ค.
๋น ๋ฐฐ์ด์ ๋ฃ์ด ์ฒ์ ์ปดํฌ๋ํธ๊ฐ ์คํ๋ ๋ ํ๋ฒ๋ง Effect๊ฐ ์คํ๋๊ฒ ํ๋ค.
๋ก์ปฌ์คํ ๋ฆฌ์ง์ ์ ์ฅ๋ ํธ์์ด ์์ผ๋ฉด ํด๋น ๋ฐ์ดํฐ๋ฅผ tweetList๋ก ์ธํ ํ๊ณ , nextId๋ฅผ ์ ์ฅ๋ ํธ์๋ค์ ์ฒซ๋ฒ์งธ ํธ์๋ณด๋ค 1์ฆ๊ฐํ๋๋ก ์์ฑํ๋ค.
์ ์ฅ๋ ํธ์์ด ์์ ๊ฒฝ์ฐ ๋๋ฏธ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ฒ ํ๊ณ nextId๋ฅผ 6์ผ๋ก ์ค์ ํด์ฃผ์๋ค.(๋๋ฏธ๋ฐ์ดํฐ์ ๋ฐ์ดํฐ๊ฐ ๋ค์ฏ๊ฐ์์ผ๋ก)
Tweets.js
import { useState, useRef, useEffect } from "react";
import Tweet from "../Components/Tweet";
import dummyTweets from "../static/dummyData";
const Tweets = () => {
const [tweetList, setTweetList] = useState([]);
const [user, setUser] = useState("harry00700");
const [msg, setMsg] = useState("");
const nextId = useRef(6);
const getTweetList = JSON.parse(localStorage.getItem("tweetAdded"));
useEffect(() => {
if (getTweetList !== null) {
setTweetList(getTweetList);
nextId.current = getTweetList[0].id + 1;
} else {
setTweetList(dummyTweets);
nextId.current = 6;
}
}, []);
const handleUser = (event) => {
setUser(event.target.value);
};
const handleMsg = (event) => {
setMsg(event.target.value);
};
const addTweet = () => {
if (msg === "") {
alert("ํธ์ ๋ด์ฉ์ ์ ์ด์ฃผ์ธ์.");
} else {
const tweet = {
id: nextId.current,
username: user,
picture: `https://randomuser.me/api/portraits/women/50.jpg`,
content: msg,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
setTweetList([tweet, ...tweetList]);
setUser("harry00700");
setMsg("");
nextId.current += 1;
localStorage.setItem("tweetAdded", JSON.stringify([tweet, ...tweetList]));
}
};
const deleteTweet = (id) => {
const deleteList = tweetList.filter((tweet) => tweet.id !== id);
setTweetList(deleteList);
localStorage.setItem("tweetAdded", JSON.stringify(deleteList));
};
const updateTweet = (id, content) => {
const updateList = tweetList.map((tweet) =>
tweet.id === id
? { ...tweet, content, updatedAt: new Date().toISOString() }
: tweet
);
setTweetList(updateList);
localStorage.setItem("tweetAdded", JSON.stringify(updateList));
};
return (
<div className="tweets">
<div className="tweet-form">
<div className="tweet-avatar">
<img src="https://randomuser.me/api/portraits/women/50.jpg" />
</div>
<div className="tweet-input-wrapper">
<div>
<input type="text" value={user} onChange={handleUser} />
<textarea value={msg} onChange={handleMsg}></textarea>
</div>
<div className="tweet-buttons">
<button type="button" onClick={addTweet}>
send
</button>
<button onClick={() => localStorage.clear("tweetAdded")}>
localstorage clear
</button>
</div>
</div>
</div>
<ul className="tweet-list">
{tweetList.map((tweet) => {
return (
<Tweet
tweet={tweet}
key={tweet.id}
deleteTweet={deleteTweet}
updateTweet={updateTweet}
/>
);
})}
</ul>
</div>
);
};
export default Tweets;
๐ฌ ๋ ๊ตฌํํ๊ณ ์ถ๊ฑฐ๋ ์์ฌ์ด ๋ถ๋ถ
ํธ์์ ์์ ํ ๋ ํ์ฌ ์์ฑ๋ ํธ์์ ํฌ๊ธฐ๋งํผ textarea์ ํฌ๊ธฐ๋ฅผ ์ง์ ํด์ฃผ๊ณ ์ถ์๋ฐ ๊ทธ๊ฒ ์๋๋ค...
useRef๋ฅผ ์ฌ์ฉํด์ p์ ๋์ด๋ฅผ ๊ฐ์ ธ์ textarea์ ํฌ๊ธฐ๋ฅผ ์ง์ ํด์ฃผ๋ ค๊ณ ํ์ง๋ง ์ฝ๋๋ฅผ ๋ฐ๋ก ๋ฃ์ผ๋ textarea๊ฐ ํ์ฌ ๋ ๋๋ง์ด ๋์ด์์ง ์์์ ๊ณ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋๊ฒ ๊ฐ๋ค.
์กฐ๊ธ ๋ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๋ ค๊ณ ํ๋ค.
๐ ํ ์ด ํ๋ก์ ํธ๋ฅผ ํตํด ๋ฐฐ์ด์
- ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ ๋ฐ์ดํฐ๋ useState๋ฅผ ์ฌ์ฉํ๊ธฐ
- useRef๋ฅผ ํตํด ํน์ DOM์ ์ ํํ๊ฑฐ๋ ๋ณ์๋ฅผ ์ง์ ํ ์์์
- useEffect๋ฅผ ์ฌ์ฉํด ์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ ๋๋ง๋ ๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๊ฒ ํ ์ ์์
- state ๋์ด์ฌ๋ฆฌ๊ธฐ๋ฅผ ํตํด ํ์ ์ปดํฌ๋ํธ์ ์ด๋ฒคํธ๋ก ์์ ์ปดํฌ๋ํธ์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝ์ํฌ์์์
- ๋ฆฌ์กํธ์์ ๋ฐฐ์ด์ ๋ฐ์ดํฐ๋ฅผ ๋ฑ๋ก / ์์ / ์ญ์ ํ๊ธฐ
- ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ผ๋ก ํน์ ์ํฉ์์ ๋ค๋ฅธ ์๋ฆฌ๋จผํธ๊ฐ ๋ํ๋๊ฒ ํ๊ธฐ
์ง์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด๋๊น ๋ฐฐ์ด๊ฒ ์ข๋ ์ดํด๊ฐ ์๋๋ค!
ํธ์ ์์ ํ๋ ๋ถ๋ถ์ ์๊ฐ์ ๋ค์ฌ์ ์กฐ๊ธ ๋ ์์ ํด๋ณผ ์๊ฐ์ด๋ค.
'study > React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[React] ๋ฐ๋๋ผ JS ๊ฒ์ํ ๋ฆฌํฉํ ๋ง (2) - Delete, Update (0) | 2023.02.16 |
---|---|
[React] ๋ฐ๋๋ผ JS ๊ฒ์ํ ๋ฆฌํฉํ ๋ง (1) - Read, Create (0) | 2023.02.11 |
[React] Fragment (0) | 2023.01.27 |
[React] defalutValue์ value (0) | 2023.01.27 |
[React] export default์ export์ ์ฐจ์ด (0) | 2023.01.25 |