2024.02.01 TIL - react router Link에 style 적용, 리액트 UD(수정 삭제), window.confirm, uuid, box-sizing

Innes·2024년 2월 1일
0

TIL(Today I Learned)

목록 보기
55/147
post-thumbnail

(참고 : react-router-dom의 Link를 styled-components로 꾸미기)

  1. 시작점
  • 지금까지 styled-components는 jsx(html) 태그에만 걸어줬는데, <Link>는 react-router-dom에서 import 해온 컴포넌트...?!
  • 이런 리액트 컴포넌트에 styled-components를 적용하려면 어떻게 해야할까?!
  1. 방법
  • styled(Link)``; 로 작성하기
    (기존 jsx 태그에는 styled.div``;이렇게 했음)
  • styled components 파일에 react-router-dom으로부터 Link import해야 함
  • 예시)
import styled from "styled-components";
import { Link } from "react-router-dom"; ⭐️

export const LinkStyle = styled(Link)` ⭐️
  text-decoration: none; // 밑줄 없애기
  color: white;
`;


2. 특정 fanLetter의 detail 페이지로 이동하기

🔥 주의 : 여러 컴포넌트에서 필요한 state들은 가장 최상위의 컴포넌트에서 갖고 있어야 함!

1) 시작점

  • <router> 사용하면서 Home, detail 페이지로 이동하기 위해 App.jsx에 있던 정보들을 Home.jsx로 모두 이동시켰는데, Home 컴포넌트에 정보가 있으니 같은 라인에 있는 형제 컴포넌트들에 (ex. Detail) 정보를 넘겨줄 수가 없어서 클릭한 fanLetter로 detail 페이지를 열리게 하는게 계속 실패함
  • 클릭한 팬레터의 정보들(닉네임, 날짜, 내용 등)을 detail 페이지에 넘겨줘야 해당 내용들을 붙일텐데, fanLetters로 관리하던 state를 넘겨주질 못해서 난관

2) 해결책

  • Home 에 전부 넣어뒀던 정보들(state들, onClick에 들어가는 함수 등)을 Router.js에 다 넣어놓고, Home, Detail에 필요한 정보들을 내려줬다.
// 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}
              />
            }
          />
// ...생략
  • detail 페이지에 받아온 정보 fanLetters를 가지고 find()함수를 이용하여 foundFanLetter 변수를 만듦 (fanLetter의 id와 params의 id가 같은 애들만 filtering)
    const foundFanLetter = fanLetters.find((fanLetter)=>{
      return fanLetter.id === params.pageId}) 
  • foundFanLetter.nickname 이런식으로 가져온 정보 사용하면 됨
    (예시)
  // 클릭한 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)

3. 수정하기

(참고 : 리액트 - 데이터 수정하기)

1) '수정' 버튼 클릭 시 textarea로 바꿔줘야됨

  • idEdit 이란 state 설정 -> false면 p태그, true면 textarea로 변경
  • 안의 내용물 역시 editedContent라는 state로 설정 -> 초기값은 fanLetter의 id와 params.pageId가 같은 fanLetter의 content로 설정
    (클릭한 fanLetter의 content를 초기값으로)
  • 클릭한 fanLetter의 정보는 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) '수정', '수정 완료' 버튼 로직 구현

  • 수정한 내용이 없을 시 '수정사항이 없습니다', 수정한 내용이 있을 시 내용 저장 후 Home으로 이동
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로 만듭니다.)

  • ⭐️ 요약
    • 수정 모드일 때:
      수정사항이 없으면 아무 동작 없이 현재 상태를 유지합니다.
      수정사항이 있으면 수정 모드를 종료하고 홈으로 이동합니다.

    • 수정 모드가 아닐 때:
      수정 모드로 전환합니다.

4. 삭제하기

  • Home화면 렌더링시 삭제한 fanLetter를 뺀 나머지만 filtering하면 됨
    -> params.pageId와 비교해야 하기 때문에 detail페이지에서 처리할 것
  • 삭제버튼 클릭 시 Home 화면으로 이동
  const removeButtonHandler = () => {
    // 삭제할지 다시 한 번 확인받기
    
    if (window.confirm("정말 삭제하시겠습니까?")) {
      setFanLetters(
        fanLetters.filter((fanLetter) => {
          return fanLetter.id !== params.pageId;
        })
      );
      homeClick();
    } else {
    }
  };

5. window.confirm()

  • ⭐️ alert 창인데 '취소' 버튼도 있음!
  • ex) '정말 삭제하시겠습니까?' (확인, 취소 버튼 있어서 선택할 수 있음)
 const onRemove = () => {
    if (window.confirm("정말 삭제합니까?")) {
      alert("삭제되었습니다.");
    } else {
      alert("취소합니다.");
    }
  };

6. 해당 멤버에게 팬레터가 없을때 '팬레터 없음' 출력

  • fanLetter.writedTo === active 먼저 filtering 하고 변수로 받아놓기(activeFilteredFanLetter)
  // 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>
    )}

7. uuid로 고유한 id 생성하기

(참고 : 리액트 고유한 Key 값 생성하기 (uuid))

  • uuid : 고유한 key, id값 생성해줌 (문자열)
  • 예시)
uuid() 
// e9ee74a7-cf11-4fac-8b77-61b51c7d636b
  • 사용방법
    • uuid 패키지 설치 : npm install uuid
    • import : import { v4 as uuidv4 } from "uuid";
    • id값 생성 : id: uuidv4()

8. box-sizing 을 border box로 설정하는 이유

(참고 : css에서 box-sizing 을 border-box로 하는 이유)

  • box-sizing을 border box로 하지 않으면 길이의 기준이 content(내용물)이 되어서, 만약 padding을 준다면 content 길이는 그대로 있고, 거기에 padding값이 더해져서 총 box의 길이는 내가 부여한 길이보다 길어지게 됨
  • 따라서, box-sizing을 border box로 주면, 기준이 content가 아니라 box길이가 기준이 돼서, content 그대로 있고 padding을 주더라도 내가 부여한 길이 안에서 수정됨
    -> box길이는 내가 부여한 길이로 유지됨
profile
무서운 속도로 흡수하는 스폰지 개발자 🧽

0개의 댓글