Skip to content

wanted-projects/wanted-codestates-project-7-8

 
 

Repository files navigation

wanted-codestates-project-7-8

충청북도 휴양림 중 마음에 드는 곳에 메모를 남겨 저장하는 서비스입니다.

프로젝트 실행 방법

  1. git clone https://github.com/Pre-Onboarding-FE-Team07/wanted-codestates-project-7-8.git
  2. npm install
  3. npm run start

프로젝트 구조

--📁 src
  ---📁 atoms ➡ Recoil에서 사용되는 atom, selector 등을 모아둔 폴더
  ---📁 components ➡ 컴포넌트 폴더
  ---📁 constants ➡ 전역 상수 폴더
  -- 📁 pages ➡ 페이지 컴포넌트
  -- 📁 utilities ➡ 모듈화된 함수를 모아둔 폴더

기술 스택


팀 멤버

이름 직책 역할
✨김정훈 팀원 휴양림 API 호출 & 데이터 렌더링 (Main Page 구현)
⚡️박진용 팀원 무한 스크롤 구현 및 PageList 완성
🎨문선경 팀장 휴양림 저장/삭제/수정 기능 구현
🚀심채윤 팀원 유저 피드백 (Toast UI) 구현
✏️예효은 팀원 휴양림 저장/삭제/수정 기능 구현
🔨이예지 팀원 로컬 스토리지에 데이터 저장
🚚최민우 팀원 검색 기능 구현

구현한 기능 목록

  • API 호출을 통해 휴양림 목록 불러오기
  • 무한 스크롤 구현 및 PageList 완성
  • 휴양림 정보 클릭 시 저장/수정이 가능한 모달창 표시
  • 휴양림 정보 저장/수정/삭제
  • 저장된 휴양림 목록 검색
  • 유저의 활동에 해당하는 toast 표시

김정훈

  • API 호출을 통해 휴양림 목록 불러오기

구현한 방법

  • Recoil 상태 관리 라이브러리를 이용해 Selector를 통한 비동기처리 방식으로 구현

어려웠던 점 (에러 핸들링)

  • 초기 구현 당시 api 호출 데이터를 로컬스토리지에 저장하는 방법으로 진행하였으나 다른 컴포넌트에서 해당 데이터가 추가적으로 필요함을 인지하였고, Recoil을 통한 상태관리의 필요성을 느끼게 되었습니다. Recoil에는 atom과 selector 함수를 통해 상태를 관리할 수 있는 방법이 있었고 atom을 이용할 경우 해당컴포넌트에서 비동기 호출이 이루어진 값을 저장하는 방법이 있었으나 selector 에서는 이러한 로직을 한번에 처리해 줄 수 있는 동시에 캐싱 기능이 있어 이미 받아왔던 정보에 대해서는 빠른 피드백이 가능해 성능적으로 유리하기 때문에 selector를 이용하여 해결할 수 있었습니다.

박진용

구현한 방법

제출한 PR 목록

Name Description Type Default
data 데이터 목록입니다. any[] -
threshold onReachScrollEnd 함수가 호출되는 범위를 결정합니다. number 0
renderItem React 요소를 반환하는 함수입니다. data 배열의 요소가 인자로 전달됩니다. (item) => ReactNode -
keyExtractor data 배열의 요소에서 key 용도의 값을 추출합니다. (item) => string -
onReachScrollEnd 스크롤의 마지막에 도달하면 호출되는 함수입니다. threshold 속성에 의해 실행시점이 변경될 수 있습니다. () => void -
  • 맨 밑까지 스크롤하면 onReachScrollEnd 함수가 자동으로 호출되도록 IntersectionObserver API를 이용해 구현했습니다.

  • threshold 속성을 통해 맨 밑이 아닌 어느 정도 밑이면 onReachScrollEnd 함수가 호출되도록 변경할 수 있습니다.

  • Main 페이지에서 사용하는 CardContainerCard 컴포넌트가 동일하므로, 이를 별도의 컴포넌트로 분리하여 PageList 페이지에서 또한 호출했습니다. CardContainer는 무한 스크롤 기능이 필요하므로 ScrollView 컴포넌트를 기반으로 하여 생성했습니다.

  • PageList에서 Card를 클릭했을 때, Modal 컴포넌트에 해당 CardcardData를 전달해 메모를 추가하여 저장할 수 있도록 둘을 연결했습니다.

어려웠던 점 (에러 핸들링)

  • IntersectionObserver에서 options로 받는 threshold는 관찰 중인 요소가 일부라도 root와 겹쳐야 필요한 값이었기 때문에, 그것보다도 미리 onReachScrollEnd 함수를 호출하려는 의도에는 적합하지 않았습니다. 대신 rootMargin의 bottom 값을 threshold로 설정하여 관찰 중인 요소의 위치와 상관없이 스크롤의 높이에 기반하여 호출을 결정하게끔 구현하는 방식으로 문제를 해결했습니다.

  • IntersectionObserver의 rootul 태그의 높이가 정해지지 않을 경우(실제로 CardContainer의 경우 height를 지정하지 않았습니다.), 자식 요소는 항상 rootul 태그 안에서 관찰되기 때문에 스크롤 높이와 무관하게 onReachScrollEnd 함수가 계속 호출되는 문제가 있었습니다. root의 높이가 뷰포트의 높이보다 커질 경우, rootnull로 설정하여 뷰포트를 기준으로 관찰하도록 하는 방법으로 문제를 해결했습니다.

const isHeightTooLarge = containerRef.current.clientHeight >= window.innerHeight;
const root = isHeightTooLarge ? null : containerRef.current; // container의 높이가 window의 높이보다 커진다면 root를 null로 설정합니다.
  • renderItem 함수가 반환하는 ReactNode를 map의 결과값으로 표현하기 위해 여러 방법을 시도했습니다.

    • renderItem의 결과를 그대로 반환합니다. 하지만, 이 방법은 key를 포함하지 않습니다.

      {data?.map((item) => renderItem(item))}
      
      // Warning: Each child in a list should have a unique "key" prop.
    • renderItem의 결과를 변수로 받아 key를 추가하여 반환하도록 시도해보았습니다. 하지만, renderItem이 반환하는 값은 component가 아닌 element이므로 타입 에러가 발생했습니다.

      {data?.map((item) => {
        const Item = renderItem(item);
        return <Item key={keyExtractor(item)} />;
      })}
      
      // Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
    • Itemkey 프로퍼티에 직접 할당하는 방법으로 시도해보았으나 key 프로퍼티는 읽기 전용 속성이라 불가능했습니다.

      {data?.map((item) => {
        const Item = renderItem(item);
        Item.key = keyExtractor(item);
        return Item;
      })}
      
      // Uncaught TypeError: Cannot assign to read only property 'key' of object '#<Object>'
    • key를 element에 할당하는 방법을 조사해보았고 cloneElement 함수를 통해 문제를 해결할 수 있었습니다.

      {data?.map((item) => cloneElement(renderItem(item), { key: keyExtractor(item) }))}

문선경

  • 프로젝트 환경설정
  • 휴양림 저장/삭제/수정 기능 구현

구현한 방법

  1. 프로젝트 환경설정
  • CRA, npm을 사용해 기본적인 프로젝트 환경 설정을 하고, recoil, emotion/styled 등 사용할 패키지들을 추가로 설치함.
  1. 휴양림 저장/삭제/수정 기능 구현
  • 예효은님이 모달 컴포넌트의 UI 부분을 구현하셨고,
    (type prop에 따라 저장 폼/수정 폼으로 UI가 변경되는 것, 모달 외부를 클릭하면 모달창이 닫히는 것 등)
    나는 Modal 컴포넌트에서 저장/수정/삭제가 이루어지는 로직 부분을 작성했다.

  • 데이터를 저장/수정하는 saveData(), 데이터를 삭제하는 removeData() 함수를 작성함.

  • saveData() : cardData.memo를 유저가 작성한 memo로 저장/수정하고, userList에 변경된 cardData를 반영.
    (수정/저장 버튼 클릭 시 호출)

  • removeData() : userList에서 선택된 cardData를 삭제. (삭제 버튼 클릭 시 호출)

어려웠던 점 (에러 핸들링)

  1. eslint, prettier 충돌 문제
  • eslint, prettier 충돌이 발생했는데, 처음에는 구현을 빠르게 시작하기 위해서
    우선 eslintrc에서 prettier를 무시하도록 설정했었음.

  • 나중에 eslint, prettier 충돌을 제대로 해결하기 위해 설정을 바꾸는 과정에서 발생하는 여러 에러가 발생함.

  • https://seomoon.tech/85 에서와 같이 디버깅하여 해결.

  1. 코드 스타일 컨벤션이 지켜지지 않은 PR이 올라오는 문제
  • husky로 git pre-commit hook을 설정. 깃 커밋 전에 lint-staged가 실행되고 lint 적용이 잘 된 경우에만 커밋을 할 수 있도록 강제해서 해결함.
  1. OS별로 (Windows, MacOS) 개행 방식이 달라서, windows에서 코드 작성 시 Lint 에러가 뜨는 불편함
  • .gitattributes 설정 파일 추가해 windows에서 crlf 개행이 lint 에러로 인식되지 않고, 커밋할 때는 crlf 개행이 lf 개행으로 전환되도록 설정해뒀는데, 제대로 적용이 되는지는 테스트가 필요한 상태임.

심채윤

유저 피드백 (Toast UI) 구현

구현한 방법

useState를 사용하여 버튼 클릭 시 handleToast 함수를 통해 setToastStatus(true)를 적용하여 모달창에서 수정, 삭제 클릭 시 Toast UI가 화면에 보이게 하였습니다. 또한 삭제, 작성, 저장의 문구가 보일 메세지를 객체로 따로 관리하여 state로 같이 관리 할 수 있게 구현하였습니다. setTimeout을 사용하여 toastStatustrue인 경우 3초 뒤 자동으로 닫히게 하고, setToastMsg를 빈 문자열로 해주었습니다. 이 때, Toast UI가 서서히 생기고 사라지는 효과를 주기 위해 keyframe 속성을 사용하여 fadeIn, fadeout을 주어 사라지는 효과를 주었습니다.

어려웠던 점 (에러 핸들링)

Toast UImodal창의 수정, 삭제 버튼 클릭 시 그에 맞는 피드백 메세지를 화면에 보여주려 했습니다. 피드백 메세지를 배열, 객체 등 어떤 식으로 구현을 해야 할지 고민하다 객체로 만들고 state로 같이 관리하게 해주었습니다. handleToast함수를 통해 setToastState(true), setToastMsg(msgLisg[select])로 모달창에서 버튼 클릭 시 그에 맞는 메세지 Toast UI를 구현하였습니다.

예효은

휴양림 저장/삭제/수정 기능 구현

구현한 방법

휴양림 저장과 수정에 쓰이는 폼의 레이아웃이 거의 동일해서 공통의 <Modal/> 컴포넌트 하나를 만들고 props 값을 다르게 주어 각각의 폼을 만들 수 있게 하였다. props로는 type을 두어 add인지 edit인지 전달받고 edit type의 modal 컴포넌트를 요청하는 거라면 메모 영역은 input 태그로 수정가능하게 하였고, 버튼은 수정,삭제 버튼이 나타나도록 처리했다. 일반적인 모달창이 그러하듯 외부 클릭시 close 처리가 되도록 모달창의 가장 바깥 영역인 ModalContainer에 click 이벤트를 적용하고 중앙의 모달창 영역인 ModalBox에는 click 이벤트에 e.stopPropagtion을 두어 ModalBox를 클릭했을 때는 ModalContainer로 클릭이벤트가 전파되지 않도록 처리했다.

어려웠던 점 (에러 핸들링)

Modal컴포넌트가 닫힌 후, 다시 열리지 않는 문제가 있었다. 우선 처음 작성한 코드는 Modal 컴포넌트에서 isOpen state를 true로 초기화하고 closeModal 함수가 호출되면 false로 변경하도록 구현했었다. 하지만 state 값을 초기화하는 것은 첫 렌더링 시에만 이루어지는 것이고, Modal 컴포넌트는 카드 데이터를 클릭할 때마다 새로 생성되는 것이 아니라 전달하는 cardData props만 변경된다는 것을 놓치고 있었다. 이에 cardData가 변경될 때마다 isOpen state를 true로 업데이트하여 모달창이 열릴 수 있게 변경하였다.

이예지

구현한 방법

Recoil을 처음 사용해본 거라 공식문서와 유튜브 영상을 보면서 Recoil에 대해서 파악을 했었습니다. 찾아보면서 Recoil을 이용해서 로컬 스토리지에 저장하는 코드를 찾게 되었고, 그 코드가 의미하는 것이 무엇인지 파악하려고 하였습니다. 여러 문서와 블로그에 대해 찾아보면서 테스트를 해 본 결과 코드에 대해 알 수 있었습니다.

어려웠던 점 (에러 핸들링)

공공 데이터 api를 이용한 cors에 대한 오류 부분이 어려웠습니다. proxy를 이용하는 http-proxy-middleware 라이브러리를 사용하는 방법도 생각하여 코드를 작성하기도 했었지만, 이 방법도 배포 시에도 해결되는 것이 아니라고 생각하였습니다. 결국 다른 팀원 분께서 생각하셨던 프록시 서버 URL을 붙여서 요청하는 방법이 더 배포하기에도 편리할 것 같다고 생각해서 그 방법으로 결정하였습니다. 그 후 netlify로 배포 후에도 CORS오류가 났었고, 블로그 또는 팀원들과 함께 문제를 해결할 수 있었습니다.

최민우

  • 검색 기능 구현
  • 저장된 목록 페이지 (<Main />, '/') UI 구현

구현한 방법

  • useRecoilValue를 통해 불러온 값을 이용하여 필터링 하였다.
  • 필터링한 값을 Map() Method를 사용하여 렌더링 하였다.
  • 클릭하는 경우 <Modal /> 컴포넌트로 연계하도록 하였다.
  • + 버튼을 누르면 추가 페이지 (<PageList />, '/list') 페이지로 이동하도록 하였다.

어려웠던 점 (에러 핸들링)

  • Prettier를 위주로 사용하고 Eslint를 잘 사용하지 않았는데, 초기 commit 단계에서 정확한 설정을 찾는데 어려움이 있었다.
    • 검색과 실험을 통해 정확한 키와 값을 알게 되었으며, 시간이 소요 되었다.
  • 윈도우에서 개발을 하게 되었는데 CRLF 관련한 문제가 발생했었다.
    • 검색을 통해 .eslintrc를 수정하였으며, git config를 수정하여 해결하였다.
  • <Modal /> 페이지와의 연계 과정에서 조율이 필요했다.
    • 메신저를 통한 토의로 트러블슈팅을 신속하게 할 수 있었다.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 98.0%
  • HTML 1.7%
  • Shell 0.3%