Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[김찬기] Sprint6 #222

Conversation

cksrlcks
Copy link
Collaborator

@cksrlcks cksrlcks commented Nov 27, 2024

요구사항

기본

상품 등록

  • 상품 등록 페이지 주소는 “/additem” 입니다.
  • 페이지 주소가 “/additem” 일때 상단네비게이션바의 '중고마켓' 버튼의 색상은 “3692FF”입니다.
  • 상품 이미지는 최대 한개 업로드가 가능합니다.
  • 각 input의 placeholder 값을 정확히 입력해주세요.
  • 이미지를 제외하고 input 에 모든 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.
  • API를 통한 상품 등록은 추후 미션에서 적용합니다.

심화

상품 등록

  • 이미지 안의 X 버튼을 누르면 이미지가 삭제됩니다.
  • 추가된 태그 안의 X 버튼을 누르면 해당 태그는 삭제됩니다.

주요 변경사항

프로젝트 셋팅

변경사항

  • useForm이 숫자,배열형태의 데이터도 validation을 할 수 있도록 개선하였습니다.
    • 최대한 직접 만든 hook을 써보다가 추후에 라이브러리 도입해보겠습니다.
    • 처음에는 태그배열도 공백이나 쉼표등으로 구분된 string 덩어리로 관리하고 전송 또는 랜더링시에 잘라서 표현하려고 했습니다.
    • 보내야하는 규격에서 벗어난 형태로 관리하는게 부담이 되어서, 입력데이터 자체를 최대한 규격에 맞게 관리해보려고 했습니다.
  • 수요일 멘토링시간에 학습내용으로 폴더구조, 파일명, 디자인패턴(presentational,container pattern)을 적용하여 전반적으로 수정했습니다.
    • 컴포넌트 각폴더에서 index.js로 내부 컴포넌트를 export 하도록 변경
    • 컴포넌트 내부에 ui폴더 생성후 ui관련된 컴포넌트를 모아서 export 하도록 변경
    • 페이지 파일에서는 컨테이너 역활로 데이터와 이벤트 핸들러를 만들어 컴포넌트에 전달
    • 불필요하게 상태를 가지는 컴포넌트들 stateless하게 변경
  • 파일업로드 로직의 재사용을 위해 useSingleFile 훅을 만들어서 사용했습니다.
  • (추가작업) 등록페이지에서 가격을 입력시 '원'단위로 바뀌게 개선해보았습니다. (NumberInput 컴포넌트가 formatter를 prop으로 선택적으로 받도록 해서 재사용성도 높였습니다.)
  • (추가작업) auth context안에서 access token을 넣어서 비동기 요청을 할 수 있는 wrapper 함수를 만들어서 '상품등록, 이미지업로드'페이지에서 사용했습니다.
  • (추가작업) 상품등록 페이지는 protected route 컴포넌트로 감싸서, accesstoken이 없으면 로그인하도록 작성했습니다.
  • (추가작업) 통신실패시 refresh token으로 access token을 다시 받아온후 재요청을 하는 로직도 추가했습니다.
  • (추가작업) 중고마켓 리스트에서 검색창을 누를시 '최근검색어' 기능을 사용할 수 있게 구현해 보았습니다. (로컬스토리지 학습내용 복습)

스크린샷

sprint6-1 sprint6-2

멘토에게

  • access token이 필요한 요청일 경우에, 모든 통신실패에 대해서 재발급된 token으로 재요청하는 프로세스가 필요한걸까요?
  • 다음 과제에 axios를 설치해서 위 사항에 대해서 interceptor를 한번 사용해보려고합니다.
  • 컴포넌트를 재사용가능하게 만드는것에 대해서 더 고민해보고 적용해보겠습니다.
  • 이미지 업로드시 500에러가 뜨는데, 이미지 주소를 임의로 넣어서 테스트했습니다.
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

- isFormValid 체크하는 로직 수정 (문자열인지, 파일 같은 값인지에 따라 체크하도록)
- register로 보내는 onChange는 input의 onChange 전용으로 수정
- hook 에서 반환하는 onChange는 name, value를 받아서 직접 업데이트 하는 함수로 수정
- required 벨리데이션 수정 (문자열인지 체크후 검증)
- rule이 없는 필드일 경우 에러가 안나도록 수정
- 숫자형태도 값을 받을 수 있도록 수정
- 보기힘든 삼항연산자 중첩 부분 리팩토링
- trigger에서 빈값이 들어가는 부분 수정
- 요청실패시 리프레시토큰이 있으면 토큰재발급후 재요청 로직 추가
- 엑세스토큰을 담아서 요청하는 통신 함수 추가
- accesstoken이 변경되면 유저정보를 가져오도록 useEffect 개선
- useAuth에서 asnycWithAuth를 가져와서 제출하는 방식으로 수정
- 에러블럭 추가
- 이미지도 필수로 넣게 조정
- user정보는 로컬스토리지에 저장안하므로 토큰을 검사
- user정보는 새로고침후 새로 가져오기전까지는 계속 null상태임
- input, textarea, tags, file에 공통적인 container 컴포넌트화
- 폼 필드 컴포넌트를 Field안에서 모아서 export
- 최근검색어가 검색인풋을 자식으로 받아서 랜더링 (검색인풋 단독으로도 사용가능하도록)
- section header 스타일링 부분 수정
- asyncWithAuth 함수명 변경 (withAuth : 비동기함수를 반환만 해주는 일반 함수라는 느낌으로)
- withAuth가 받는 함수실행문을 감싸도록 개선
- 검색인풋은 내부 state없이 uncontrolled로 개선
- 최근검색에서 children에게 props전달하도록 개선
- stateless하게 변경 (부모 컴포넌트에서 state 관리)
- 결과값도 state로 관리해서 사용 할 수 있도록 개선
@cksrlcks cksrlcks force-pushed the React-김찬기-sprint6 branch from defb4b6 to b34e60f Compare November 28, 2024 03:43
Comment on lines +85 to +106
function withAuth(asyncFn) {
return async function (...args) {
try {
return await asyncFn(...args, auth.accessToken);
} catch (err) {
if (err.status === 401 && auth.refreshToken) {
try {
const newAccessToken = await handleRefreshToken();
return await asyncFn(...args, newAccessToken);
} catch (refreshErr) {
console.error("리프레시 요청 실패");
clear();
throw refreshErr;
}
} else {
console.error(err);
throw err;
}
}
};
}

Copy link
Collaborator Author

@cksrlcks cksrlcks Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드잇 강의에서 배운 wrapperFunc을 응용해보려고 했습니다.
받은 함수를 다시 돌려주는데 받은함수의 인자와 함께 토큰을 같이 넣어서 보내는 형식으로 작성했습니다.
(보충수업에서 알려주신 axios interceptor를 이용하는 방법을 다음주에 적용해보려고합니다.)

- 전달받는 인자를 간략하게 바꿈
- 전달받는 객체를 분해하여, 의존성배열에 삽입
- useList훅, 최근검색훅 개선으로 인한 전체 상품페이지 개선
- 그룹내 페이저갯수 구하는 식에서 불필요한 Math.max 삭제(항상 0보다 크게 나와서 불필함)
- App.jsx로 provider들 가져오 (index.jsx는 단순하게 두려고함)
- router 정의에 루트패스 작성
- 엔터를 쳐서 태그를 입력후, blur해버리니 사용성이 안좋아서 blur를 삭제
- 한글로 태그 입력시 마지막 문자가 분리되어서 태그가 입력되는 형상을 keyup이벤트로 교체했었는데, 문제의 원인을 구글링을 통해서 알게되어 해결 (한글을 입력시 합성되고 있으면 제출안되도록)
Comment on lines +1 to +64
import { useState } from "react";
import useLocalStorage from "@hooks/useLocalStorage";

const RECENT_SEARCH_SIZE = 5;

export default function useRecentSearch({ initialKeyword, onChange }) {
const [searchInput, setSearchInput] = useState(initialKeyword || "");
const [recentSearh, setRecentSearch] = useLocalStorage("keyword", []);

// 검색창 핸들러 (submit, change, clear)
function handleSearchSubmit() {
if (searchInput.trim()) {
setRecentSearch((prev) =>
[
searchInput,
...prev.filter((keyword) => keyword !== searchInput),
].slice(0, RECENT_SEARCH_SIZE)
);
}
//검색인풋 초기화가 좋은 UX일까? (검색취소 버튼이 나오도록, 검색어가 떠있는게 좋은것 같음)
//setSearchInput("");
onChange(searchInput);
}

function handleSearchChange(e) {
setSearchInput(e.target.value);
}

function handleSearchClear() {
setSearchInput("");
onChange("");
}

// 최근검색 이벤트 핸들러 (click, remove, clear)
function handleRecentSearchClick(value) {
setRecentSearch((prev) =>
[value, ...prev.filter((keyword) => keyword !== value)].slice(
0,
RECENT_SEARCH_SIZE
)
);
setSearchInput(value);
onChange(value);
}

function handleRecentSearchRemove(value) {
setRecentSearch((prev) => prev.filter((keyword) => keyword !== value));
}

function handleRecentSearchClear() {
setRecentSearch([]);
}

return {
searchInput,
recentSearh,
handleSearchSubmit,
handleSearchChange,
handleSearchClear,
handleRecentSearchClick,
handleRecentSearchRemove,
handleRecentSearchClear,
};
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최근검색과 키워드검색이 만들어내는 핸들러양이 너무 많아서, items폴더에서만 사용하는 훅으로 따로 분리했습니다...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최근검색 추가기능까지 만들고 스프린트 미션 재미나게 하시네요~ㅋㅋㅋ
넘나 좋습니다👍

@cksrlcks cksrlcks force-pushed the React-김찬기-sprint6 branch from fd6c04e to 2d24cc0 Compare November 30, 2024 00:37
- 기존 useFilteredSearchParams 이름 변경(좀더 직관적이게)
- 관리되는 params state와 url이 싱크되는 훅
- react router의 setSearchParam의 사용을 url쿼리파라미터가 이전과 비교해서 변경되었으면 작동하도록 params를 비교하도록 추가 (lodash isEqual 사용)
- number 값을 바로 쓸 수 있도록 변경
- aspect ratio를 받아서 유동적으로 쓸 수 있도록 제작
- 업로드 및 상품리스트에서 공용으로 사용가능하도록 공용 컴포넌트로 분리
- number input에 undefined가 넘어오면 경고를 해서 개선
- 기존 formatted 값 가독성좋게 변경
- 값이 없으면 formatter를 사용안하도록 설정
Copy link
Contributor

@withyj-codeit withyj-codeit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스프린트 미션 하느라 수고 많으셨어요~👏🏻
즐기고 계신게 느껴지네요😎

- 생각해보니 useFetch는 '주소'를 받아서 fetch처리하는 훅으로 사용되어야할 것 같음 (구글링의 예제 코드들을 분석해봄)
- url주소 기반으로 처리하는 useFetch를 쓰게되면, 만약에 api url이 변경되거나 요청전 작업이 수정되면 수정하러 이곳저곳을 돌아다녀야 할 것 같음.
- 비동기 요청함수를 받아서 처리하는 훅으로 만들어둔 useAsync를 쓰는게 더 적합해보임
- 주소를 기반으로 fetch만을 처리하는 훅으로 useFetch를 수정하고 남겨두기로 결정
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고민했던 것들 정리해서 기록하는 것 매우 좋아요~👍

Copy link
Contributor

@withyj-codeit withyj-codeit Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로 data fetching 하는 hook 하나 (다른 api콜 할 때도 사용하는 공용 hook),
data fetching 하는 hook을 사용해서 실제 서비스에 필요한 api 콜하고 데이터를 받고 필요한 데이터 정제 및 일부 필요한 기능이 들어있는 hook 하나 이렇게 분리해서 사용하는게 깔끔했었어요.

참고 링크는 이후에 다루게 되는 react-query의 예시인데 useQuery같이 api 콜에 필요한 기능을 가진 hook이 있고, 그리고 이걸 래핑해서 구현해서 사용하는 custom hook으로 분리하는 방식으로 사용하는 방식이 좋았어요.

물론 간단한 요청의 경우 data fetching 하는 hook을 바로 사용 하는 것도 괜찮은 옵션이라 생각해요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리액트쿼리 적용할때 참고해서 적용해보겠습니다!

  • api요청 함수를 서비스쪽에 모아두고 이것을 활용해서 데이터 패칭을 하려니까 좀 복잡해진것 같습니다.
  • 리액트라우터의 loader쪽에도 이 함수를 재사용하고 싶어서 분리하게 되었는데,
  • 리액트쿼리 같은 라이브러리들에서도 이 서비스 함수를 이용하는 방법도 알아보려고합니당

Comment on lines +1 to +64
import { useState } from "react";
import useLocalStorage from "@hooks/useLocalStorage";

const RECENT_SEARCH_SIZE = 5;

export default function useRecentSearch({ initialKeyword, onChange }) {
const [searchInput, setSearchInput] = useState(initialKeyword || "");
const [recentSearh, setRecentSearch] = useLocalStorage("keyword", []);

// 검색창 핸들러 (submit, change, clear)
function handleSearchSubmit() {
if (searchInput.trim()) {
setRecentSearch((prev) =>
[
searchInput,
...prev.filter((keyword) => keyword !== searchInput),
].slice(0, RECENT_SEARCH_SIZE)
);
}
//검색인풋 초기화가 좋은 UX일까? (검색취소 버튼이 나오도록, 검색어가 떠있는게 좋은것 같음)
//setSearchInput("");
onChange(searchInput);
}

function handleSearchChange(e) {
setSearchInput(e.target.value);
}

function handleSearchClear() {
setSearchInput("");
onChange("");
}

// 최근검색 이벤트 핸들러 (click, remove, clear)
function handleRecentSearchClick(value) {
setRecentSearch((prev) =>
[value, ...prev.filter((keyword) => keyword !== value)].slice(
0,
RECENT_SEARCH_SIZE
)
);
setSearchInput(value);
onChange(value);
}

function handleRecentSearchRemove(value) {
setRecentSearch((prev) => prev.filter((keyword) => keyword !== value));
}

function handleRecentSearchClear() {
setRecentSearch([]);
}

return {
searchInput,
recentSearh,
handleSearchSubmit,
handleSearchChange,
handleSearchClear,
handleRecentSearchClick,
handleRecentSearchRemove,
handleRecentSearchClear,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최근검색 추가기능까지 만들고 스프린트 미션 재미나게 하시네요~ㅋㅋㅋ
넘나 좋습니다👍

].slice(0, RECENT_SEARCH_SIZE)
);
}
//검색인풋 초기화가 좋은 UX일까? (검색취소 버튼이 나오도록, 검색어가 떠있는게 좋은것 같음)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


function setValue(value) {
try {
//useState의 setState에 callback을 넘기는 행동처럼 사용가능하도록
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

const valid = value.length && !error;

function handleKeyDown(e) {
if (e.nativeEvent.isComposing) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@withyj-codeit withyj-codeit merged commit 30ceb04 into codeit-bootcamp-frontend:React-김찬기 Dec 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants