npm install react-router-dom@6.3.0
원하는 레이아웃에 끼워 넣을 수 있게 상세 컴포넌트를 먼저 구현합니다.
Sidebar.js
)Sidebar.js
파일에서 직접 확인하세요.import React from 'react';
const Sidebar = () => {
return (
<section className="sidebar">
<i className="far fa-comment-dots"></i>
<i className="far fa-question-circle"></i>
<i className="far fa-user"></i>
</section>
);
};
export default Sidebar;
Footer.js
)<footer>
가 있어야 합니다.import React from 'react';
const Footer = () => {
return (
<footer>
{/* React에서 public 폴더 내 이미지파일을 불러올 때 src={`${process.env.PUBLIC_URL}/파일명} */}
<img id="logo" src={`${process.env.PUBLIC_URL}/Twitter-logo.svg.png`} />
Copyright @ 2023 Code States
</footer>
);
};
export default Footer;
Tweet.js
)프로필 사진을 넣기 위해 <img>
엘리먼트를 작성하고, src
속성에 전달받은 props의 사진 정보가 들어가 있는지 확인해 주세요.
<img src={tweet.picture} />
유저 이름을 넣기 위해 <span>
엘리먼트를 작성하고, class 이름을 tweet__username
로 지정하세요.
<span className='tweet__username'>{tweet.username}</span>
트윗 생성 일자를 넣기 위해 <span>
엘리먼트를 작성하고, class 이름을 tweet__createdAt
으로 지정하세요.
yyyy. mm. dd.
이어야 하고, parsedDate 변수를 이용하세요.const parsedDate = new Date(tweet.createdAt).toLocaleDateString('ko-kr');
<span className='tweet__createdAt'>{parsedDate}</span>
트윗 메시지를 넣기 위해 <div>
엘리먼트가 작성되어 있습니다. class 이름을 tweet__message
로 지정되어 있는지 확인해 주세요.
<div className="tweet__message">{tweet.content}</div>
dummyTweets
가 아닌 다른 데이터가 props로 전달되어도 트윗 정보를 정확하게 표시해야 합니다.import React from 'react';
import './Tweet.css';
const Tweet = ({ tweet }) => {
const parsedDate = new Date(tweet.createdAt).toLocaleDateString('ko-kr');
return (
<li className="tweet" id={tweet.id}>
<div className="tweet__profile">
<img src={tweet.picture} />
</div>
<div className="tweet__content">
<div className="tweet__userInfo">
<div className="tweet__userInfo--wrapper">
<span className='tweet__username'>{tweet.username}</span>
<span className='tweet__createdAt'>{parsedDate}</span>
</div>
</div>
<div className="tweet__message">{tweet.content}</div>
</div>
</li>
);
};
export default Tweet;
페이지를 구성하는 컴포넌트를 작성합니다.
이번 과제의 현재 유저는 parkhacker이기 때문에, Twittler의 MyPage에서 parkhacker의 트윗만 보여야 하는 요구사항을 만족해야 합니다.
About.js
)import React from 'react';
import Footer from '../Footer';
import './About.css';
const About = (props) => {
return (
<section className="aboutTwittler">
<div className="aboutTwittler__container">
<div className="aboutTwittler__wrapper">
<div className="aboutTwittler__detail">
<p className="aboutTwittler__detailName">React Twittler Info</p>
</div>
</div>
</div>
<div className="aboutTwittler__content">
<i className="fas fa-users"></i>
<p>나만의 Twittler 소개페이지를 꾸며보세요.</p>
</div>
<Footer />
</section>
);
};
export default About;
MyPage.js
)const filteredTweets = dummyTweets.filter(tweet => tweet.username === 'parkhacker');
filteredTweets
을 활용해 parkhacker의 트윗이 보이도록 구현해 주세요.<ul className="tweets__mypage">
{filteredTweets.map(tweet => {
// Tweet 컴포넌트에 tweet, key를 props로 전달
return <Tweet tweet={tweet} key={tweet.id} />
})}
</ul>
return (
<section className="myInfo">
...(생략)
<Footer />
</section>
);
import React from 'react';
import Footer from '../Footer';
import Tweet from '../Components/Tweet';
import './MyPage.css';
import dummyTweets from '../static/dummyData';
const MyPage = () => {
const filteredTweets = dummyTweets.filter(tweet => tweet.username === 'parkhacker');
return (
<section className="myInfo">
<div className="myInfo__container">
<div className="myInfo__wrapper">
<div className="myInfo__profile">
<img src={filteredTweets[0].picture} />
</div>
<div className="myInfo__detail">
<p className="myInfo__detailName">
{filteredTweets[0].username} Profile
</p>
<p>28 팔로워 100 팔로잉</p>
</div>
</div>
</div>
<ul className="tweets__mypage">
{filteredTweets.map(tweet => {
return <Tweet tweet={tweet} key={tweet.id} />
})}
</ul>
<Footer />
</section>
);
};
export default MyPage;
Tweets.js
)<React.Fragment>
...(생략)
<ul className="tweets">
{dummyTweets.map((tweet) => {
return <Tweet tweet={tweet} key={tweet.id} />;
})}
</ul>
<Footer />
</React.Fragment>
이전 React Twittler SPA 과제에서 배운 내용을 바탕으로 아래 기술 요구사항을 구현합니다.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const App = (props) => {
return (
<BrowserRouter>
<div className="App">
<main>
<Sidebar />
<section className="features">
<Routes>
<Route path='/' element={<Tweets />} />
<Route path='/about' element={<About />} />
<Route path='/mypage' element={<MyPage />} />
</Routes>
</section>
</main>
</div>
</BrowserRouter>
);
};
import { Link } from 'react-router-dom';
const Sidebar = () => {
return (
<section className="sidebar">
<Link to='/'>
<i className="far fa-comment-dots"></i>
</Link>
<Link to='/about'>
<i className="far fa-question-circle"></i>
</Link>
<Link to='/mypage'>
<i className="far fa-user"></i>
</Link>
</section>
);
};
/
이어야 합니다./about
으로 라우트 되어야 합니다./mypage
로 라우트 되어야 합니다.Tweets 컴포넌트에서 어떤 데이터가 state가 되어야 하고, 어떤 데이터를 props로 하위 컴포넌트에 전달해야 할지 고민하고 아래 기술 요구사항을 구현합니다.
<input>
의 값이 변경될 때 onChange 이벤트 핸들러가 불려야 합니다.const [username, setUsername] = useState("parkhacker");
const handleChangeUser = (event) => {
setUsername(event.target.value)
};
return (
<React.Fragment>
...(생략)
<input
type="text"
placeholder="your username here.."
className="tweetForm__input--username"
onChange={handleChangeUser}
value={username}
></input>
...(생략)
</React.Fragment>
)
<textarea>
의 값이 변경될 때 onChange 이벤트 핸들러가 불려야 합니다.const [msg, setMsg] = useState("");
const handleChangeMsg = (event) => {
setMsg(event.target.value)
};
return (
<React.Fragment>
...(생략)
<textarea
placeholder="트윗 내용을 입력하세요."
className="tweetForm__input--message"
value={msg}
onChange={handleChangeMsg}
></textarea>
...(생략)
</React.Fragment>
)
<button>
의 값이 변경될 때 onClick 이벤트 핸들러가 불려야 합니다.const [tweets, setTweets] = useState(dummyTweets);
const handleButtonClick = () => {
const tweet = {
id: tweets.length + 1,
username: username,
picture: `https://randomuser.me/api/portraits/men/98.jpg`,
content: msg,
createdAt: new Date(),
updatedAt: new Date()
};
setTweets([tweet, ...tweets]); // 새로운 tweet를 앞에, 기존 tweets는 스프레드 연산자로 복사
setMsg(''); // button 클릭 시 textarea의 value 초기화
return (
<React.Fragment>
...(생략)
<button
type="button"
className="tweetForm__submitButton"
onClick={handleButtonClick}
>
전송
</button>
...(생략)
</React.Fragment>
)
기존 dummyTweets
를 모두 보여줘야 합니다.
새로 추가된 트윗을 포함하여 보여줘야 합니다.
새로 추가된 트윗이 최상단에 위치하여야 합니다.
import React, { useState } from "react";
import Footer from "../Footer";
import Tweet from "../Components/Tweet";
import "./Tweets.css";
import dummyTweets from "../static/dummyData";
const Tweets = () => {
const [tweets, setTweets] = useState(dummyTweets);
const [username, setUsername] = useState("parkhacker");
const [msg, setMsg] = useState("");
const handleButtonClick = () => {
const tweet = {
id: tweets.length + 1,
username: username,
picture: `https://randomuser.me/api/portraits/men/98.jpg`,
content: msg,
createdAt: new Date(),
updatedAt: new Date()
};
setTweets([tweet, ...tweets]);
setMsg('');
};
const handleChangeUser = (event) => {
setUsername(event.target.value)
};
const handleChangeMsg = (event) => {
setMsg(event.target.value)
};
return (
<React.Fragment> {/* 데이터 차지 없이 감쌀 수 있는 태그 */}
<div className="tweetForm__container">
<div className="tweetForm__wrapper">
<div className="tweetForm__profile">
<img src="https://randomuser.me/api/portraits/men/98.jpg" />
</div>
<div className="tweetForm__inputContainer">
<div className="tweetForm__inputWrapper">
<div className="tweetForm__input">
<input
type="text"
placeholder="your username here.."
className="tweetForm__input--username"
onChange={handleChangeUser}
value={username}
></input>
<textarea
placeholder="트윗 내용을 입력하세요."
className="tweetForm__input--message"
value={msg}
onChange={handleChangeMsg}
></textarea>
</div>
<div className="tweetForm__count" role="status">
<span className="tweetForm__count__text">
{"total: " + tweets.length}
</span>
</div>
</div>
<div className="tweetForm__submit">
<div className="tweetForm__submitIcon"></div>
<button
type="button"
className="tweetForm__submitButton"
onClick={handleButtonClick}
>
전송
</button>
</div>
</div>
</div>
</div>
<div className="tweet__selectUser"></div>
<ul className="tweets">
{tweets.map((tweet) => {
return <Tweet tweet={tweet} key={tweet.id} />;
})}
</ul>
<Footer />
</React.Fragment>
);
};
export default Tweets;