📦src
┣ 📂assets
┣ 📂components
┃ ┣ 📂Home
┃ ┃ ┣ 📜AddFanLetter.jsx
┃ ┃ ┣ 📜FanLetterItem.jsx
┃ ┃ ┣ 📜FanLetterList.jsx
┃ ┃ ┗ 📜Tab.jsx
┃ ┗ 📂UI
┃ ┃ ┣ 📜Button.jsx
┃ ┃ ┗ 📜Header.jsx
┣ 📂pages
┃ ┣ 📜Details.jsx
┃ ┗ 📜Home.jsx
┣ 📂shared
┃ ┗ 📜Router.js
┣ 📜App.js
┣ 📜FakeData.json
┣ 📜GlobalStyle.jsx
┗ 📜index.js
yarn add styled-components
yarn add react-router-dom
yarn add uuid
(src폴더 기준 절대경로 설정)
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
git checkout -b props-drilling
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Home from '../pages/Home';
import Details from '../pages/Details';
import Header from 'components/UI/Header';
const Router = () => {
return (
<BrowserRouter>
<Header />
<Routes>
<Route
path='/' element={<Home />}
/>
<Route
path='details/:id' element={<Details />}
/>
<Route />
</Routes>
</BrowserRouter>
);
};
export default Router;
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
/* reset CSS */
(...)
`;
export default GlobalStyle;
function App() {
return (
<>
<GlobalStyle />
<Router />
</>
);
}
const Btn = styled.button`
(...)
&:hover {
filter: brightness(1.2);
}
`;
Button 컴포넌트를 토대로 확장해서 사용하기
const Btn = styled.button`
background-color: #eeb20c;
(...)
`;
const HomeBtn = styled(Btn)`
display: block;
margin: 0 auto;
`;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
const [fanLetters, setFanLetters] = useState(FakeData)
<Link to={`/details/${item.id}`} key={item.id}>
<FanLetterItem item={item} />
</Link>
useParams
사용하여 id 가져와서 팬레터 상세화면 보여주고 수정과 삭제할 때도 사용const [editInputShown, setEditInputShown] = useState(false);
const [editInput, setEditInput] = useState('');
const editHandler = () => {
// 전체 팬레터에서 현재 팬레터를 선택
const selectedFanLetter = fanLetters.find((item) => item.id === id);
// 수정상태 토글 (수정상태 or 아닌상태)
setEditInputShown((editInputShown) => !editInputShown);
// 수정상태가 아니라면 원래 content를 보여줌
if (!editInputShown) { setEditInput(selectedFanLetter.content);
}
// 수정 사항이 없으면 alert를 띄우고 함수 종료
// (수정상태이고 editInput의 값이 기존의 content 내용과 같다면)
if (
editInputShown &&
editInput.trim() === selectedFanLetter.content.trim()
) {
alert('수정할 내용이 없습니다.');
return;
}
// 수정 상태라면 최신 상태를 받아와서 content만 update
if (editInputShown) {
setFanLetters((prevFanLetters) =>
prevFanLetters.map((item) =>
item.id === id ? { ...item, content: editInput } : item
)
);
}
};
{editInputShown ? (
<textarea value={editInput} onChange={editInputHandler}></textarea>
) : (
<p>{item.content}</p>
)}
const [memberClick, setMemberClick] = useState({
전체: false,
카리나: false,
윈터: false,
닝닝: false,
지젤: false,
});
const [selectedMember, setSelectedMember] = useState('전체');
const clickHandler = (e) => {
setSelectedMember(e.target.innerHTML);
setMemberClick({
...memberClick,
[selectedMember]: true,
});
};
const Tab = ({ clickHandler, selectedMember }) => {
return (
<TabGroup>
<Button onClick={clickHandler} $active={selectedMember === '전체'}>
전체
</Button>
<Button onClick={clickHandler} $active={selectedMember === '카리나'}>
카리나
</Button>
<Button onClick={clickHandler} $active={selectedMember === '윈터'}>
윈터
</Button>
<Button onClick={clickHandler} $active={selectedMember === '닝닝'}>
닝닝
</Button>
<Button onClick={clickHandler} $active={selectedMember === '지젤'}>
지젤
</Button>
</TabGroup>
);
};
const Button = ({ children, onClick, $active }) => {
return (
<Btn onClick={onClick} $active={$active}>
{children}
</Btn>
);
};
const Btn = styled.button`
(...)
${(props) =>
props.$active &&
`
background-color: #015aff;
color: #fff;
`}
`;
const FanLetterList = ({ fanLetters, selectedMember }) => {
// 선택된 멤버가 '전체'가 아니라면
// 선택된 멤버와 item의 wirtedTo(누구에게 쓸건가요?)가 일치하는 팬레터만 filter
// '전체' 라면 모두 true 처리해서 모든 팬레터가 나오도록
const filteredLetters = fanLetters.filter((item) =>
selectedMember !== '전체' ? item.writedTo === selectedMember : true
);
return (
<ScFanLetterItems>
/* 팬레터가 있거나 길이가 0보다 크다면 */
{filteredLetters && filteredLetters.length > 0
? filteredLetters.map((item) => (
<Link to={`/details/${item.id}`} key={item.id}>
<FanLetterItem item={item} />
</Link>
))
: '팬레터가 없습니다'}
</ScFanLetterItems>
);
};