(참고 : react-router-dom의 Link를 styled-components로 꾸미기)
<Link>
는 react-router-dom에서 import 해온 컴포넌트...?!styled(Link)``;
로 작성하기styled.div``;
이렇게 했음)import
해야 함import styled from "styled-components";
import { Link } from "react-router-dom"; ⭐️
export const LinkStyle = styled(Link)` ⭐️
text-decoration: none; // 밑줄 없애기
color: white;
`;
🔥 주의 : 여러 컴포넌트에서 필요한 state들은 가장 최상위의 컴포넌트에서 갖고 있어야 함!
1) 시작점
<router>
사용하면서 Home, detail 페이지로 이동하기 위해 App.jsx에 있던 정보들을 Home.jsx로 모두 이동시켰는데, Home 컴포넌트에 정보가 있으니 같은 라인에 있는 형제 컴포넌트들에 (ex. Detail) 정보를 넘겨줄 수가 없어서 클릭한 fanLetter로 detail 페이지를 열리게 하는게 계속 실패함2) 해결책
// Router에 정보들 다 넣어놓기
const Router = () => {
const [fanLetters, setFanLetters] = useState([]);
const memberArr = ["카리나", "윈터", "닝닝", "지젤"];
const [activeButton, setActiveButton] = useState("카리나");
const activeButtonChangeHandler = (member) => {
setActiveButton(member);
};
useEffect(() => {
fetch("http://localhost:4000/Dummy")
.then((response) => response.json())
.then((json) => {
setFanLetters([...json]);
return console.log(json);
});
}, []);
// 팬레터 내용 수정
const [isEdit, setIsEdit] = useState(false);
const [editedContent, setEditedContent] = useState("");
const [selectedFanLetter, setSelectedFanLetter] = useState(null);
// Router의 jsx부분
return (
<>
<GlobalStyle />
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<Home
fanLetters={fanLetters}
setFanLetters={setFanLetters}
memberArr={memberArr}
activeButton={activeButton}
setActiveButton={setActiveButton}
activeButtonChangeHandler={activeButtonChangeHandler}
editedContent={editedContent}
/>
}
/>
// ...생략
fanLetters
를 가지고 find()
함수를 이용하여 foundFanLetter 변수를 만듦 (fanLetter의 id와 params의 id가 같은 애들만 filtering)const foundFanLetter = fanLetters.find((fanLetter)=>{
return fanLetter.id === params.pageId})
// 클릭한 fanLetter로 정보 가져오기
const params = useParams();
// Router에서 내려받은 selectedFanLetter state
// const [selectedFanLetter, setSelectedFanLetter] = useState(null);
useEffect(() => {
const foundFanLetter = fanLetters.find((foundFanLetter) => {
return foundFanLetter.id === params.pageId;
});
if (foundFanLetter) {
setSelectedFanLetter(foundFanLetter);
} else {
}
}, [fanLetters, params.pageId, setSelectedFanLetter]);
🔥 주의 1.
useParams
는 detail 페이지에서 써야하는 것이었음
(router에 전역변수들이랑 같이 넣고 params도 같이 정보 내려주기 하니까 안돼서 알게됨)
🔥 주의2.
- useState는 react로 부터 import
- useNavigate, useParams는 react-router-dom로 부터 import
- 예시)
❌ Bad : import { useState, useParams } from "react" (XX)
👍🏻 Good :
import { useState } from "react"
import { useNavigate, useParams } from "react-router-dom" (00)
(참고 : 리액트 - 데이터 수정하기)
1) '수정' 버튼 클릭 시 textarea로 바꿔줘야됨
idEdit
이란 state 설정 -> false면 p태그, true면 textarea로 변경editedContent
라는 state로 설정 -> 초기값은 fanLetter의 id와 params.pageId가 같은 fanLetter의 content로 설정selectedFanLetter
라는 state로 관리 -> find()
함수로 params.pageId
와 같은 id 가진 fanLetter 찾아놓기setSelectedFanLetter
에 저장 // isEdit, editedContent 모두 Router에서 관리, props로 받아올 것
// const [isEdit, setIsEdit] = useState(false);
// const [editedContent, setEditedContent] = useState("");
useEffect(() => {
if (selectedFanLetter) {
setEditedContent(selectedFanLetter.content);
}
}, [selectedFanLetter, setEditedContent]);
// isEdit이 false 이면 <p>태그, true이면 <textarea>
{isEdit ? (
<St.EditTextarea
value={editedContent}
onChange={(event) => {
setEditedContent(event.target.value);
}}
maxLength="100"
/>
) : (
<St.NoneEditP>{editedContent}</St.NoneEditP>
)}
2) textarea일 때는 '완료'버튼으로 변경
{isEdit ? "완료" : "수정"}
3) 수정한 내용은 editedContent에 담고, 담긴 값을 fanLetters에도 반영해야 Home화면에서도 그대로 내용이 유지됨!
함수형 업데이트
이용하기!const changeContentToEditContent = (nextContent) => {
if (isEdit) {
if (window.confirm("이대로 수정하시겠습니까?")) {
setFanLetters((prevFanLetters) => { // ⭐️ 함수형 업데이트
return prevFanLetters.map((fanLetter) => {
return fanLetter.id === params.pageId
// 해당 페이지와 같은 id인 fanLetter의 content를 editedContent로 바꿔낀다.
// (nextContent자리에 editedContent가 들어옴, 아래 이어지는 내용 참고)
? { ...fanLetter, content: nextContent }
: fanLetter;
});
});
} else {
}
} else {
}
};
4) '수정', '수정 완료' 버튼 로직 구현
const clickEditHandler = () => {
changeContentToEditContent(editedContent); // 3번 내용의 함수
//
(isEdit
? () => {
if (selectedFanLetter.content === editedContent) {
alert("수정사항이 없습니다.");
} else {
setIsEdit(!isEdit);
homeClick();
}
}
: () => {
setIsEdit(!isEdit);
})();
};
💡 추가 설명 (isEdit이 false, true 변하는 조건이 계속 헷갈림)
- isEdit가 true인 경우
- selectedFanLetter.content와 editedContent가 같으면 "수정사항이 없습니다." 알림을 띄웁니다.
그렇지 않으면 setIsEdit(!isEdit)를 호출하여 isEdit를 반전시키고 (false로 만들고), 그 후 homeClick()을 호출하여 홈으로 이동합니다.
- isEdit가 false인 경우
- setIsEdit(!isEdit)를 호출하여 isEdit를 반전시킵니다. (true로 만듭니다.)
- ⭐️ 요약
- 수정 모드일 때:
수정사항이 없으면 아무 동작 없이 현재 상태를 유지합니다.
수정사항이 있으면 수정 모드를 종료하고 홈으로 이동합니다.
- 수정 모드가 아닐 때:
수정 모드로 전환합니다.
const removeButtonHandler = () => {
// 삭제할지 다시 한 번 확인받기
if (window.confirm("정말 삭제하시겠습니까?")) {
setFanLetters(
fanLetters.filter((fanLetter) => {
return fanLetter.id !== params.pageId;
})
);
homeClick();
} else {
}
};
const onRemove = () => {
if (window.confirm("정말 삭제합니까?")) {
alert("삭제되었습니다.");
} else {
alert("취소합니다.");
}
};
// activeButton과 일치하는 writedTo 가진 팬레터를 filtering
const activeFilteredFanLetter = fanLetters.filter(
(fanLetter) => fanLetter.writedTo === activeButton
);
activeFilteredFanLetter의 길이가 0보다 길면
-> activeFilteredFanLetter를 map 돌려서 fanLetter 붙이기
activeFilteredFanLetter의 길이가 0이면 (해당 멤버에게 쓰여진 fanLetter가 없음)
-> <div>팬레터가 없습니다</div>
출력
{activeFilteredFanLetter.length > 0 ? (
// ⭐️ 길이가 0보다 길면 미리 filtering 해놓은 애들 map으로 출력
activeFilteredFanLetter.map((fanLetter) => {
return (
<St.Li key={fanLetter.id}>
<St.LinkStyle to={`/detail/${fanLetter.id}`}>
<St.DivProfile>
<St.ImgProfile
src={fanLetter.avatar}
alt=""
></St.ImgProfile>
<St.DivProfileContent>
<span>{fanLetter.nickname}</span>
<time>{fanLetter.createdAt}</time>
</St.DivProfileContent>
</St.DivProfile>
<St.DivComment>
<span>{fanLetter.content}</span>
</St.DivComment>
</St.LinkStyle>
</St.Li>
);
})
) : ( // ⭐️ 그렇지 않으면 (길이가 0이면)
<St.NoneFanLetter>
아직 등록된 팬레터가 없습니다. 첫 번째 팬레터의 주인공이 되세요!
</St.NoneFanLetter>
)}
(참고 : 리액트 고유한 Key 값 생성하기 (uuid))
uuid()
// e9ee74a7-cf11-4fac-8b77-61b51c7d636b
npm install uuid
import { v4 as uuidv4 } from "uuid";
id: uuidv4()
(참고 : css에서 box-sizing 을 border-box로 하는 이유)