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

[4주차 기본/심화/생각 과제] 보리몽의 기상 예보 #8

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

borimong
Copy link
Contributor

@borimong borimong commented May 11, 2023

✨ 구현 기능 명세

🌈 구현사항

✅ 기상 카드 V

  1. 날씨에 따른 이미지
  2. 온도
  3. 체감온도
  4. 최저,최고
  5. 구름 %

✅ 일간 / 주간 + 지역(영어)검색 V

  1. 날씨 검색 타입 V
    1. 오늘 → 하루
    2. 주간 → 5일 예보
  2. 검색 기능V
    1. /day/:area or /week/:area 와 같이 params로 검색한 지역을 넘겨줍니다. :: hint) useParams
    2. 이를 가져와 오늘/ 주간 날씨를 렌더링

✅ Outlet + Route V

  1. Outlet과 Route를 활용하여 페이지 내의 컴포넌트를 변경해주세요!V
    1. 헤더는 고정
    2. 기상 정보 카드들만 경로에 따라 변경됩니다.
  2. 에러페이지 처리도 필수! V

구분선2.png

🌈 심화과제

✅ 스켈레톤 UI V

  • 기상 정보를 불러올때, 사용자에게 스켈레톤 UI를 보이게 합니다!

✅ 커스텀훅으로 데이터를 받아옵시다! V


🌼 PR Point

  • ~ 부분 이렇게 구현했어요, 피드백 부탁해요!
    ✅ week4/weather_forecast/src/pages/Week.jsx
    Outlet 을 이용해 페이지 내의 하위 컴포넌트들 간의 전환을 쉽게 해주었어요 :) {children} 과 비슷한 느낌!!
export default function Week() {
  return (
    <div>
      <Outlet />
    </div>
  )
}

✅ week4/weather_forecast/src/components/Card.jsx
커스텀 훅을 사용해서 data를 가져와 주었구요, 구조 분해 할당으로 weather, main, clouds 만 뽑아와주었어요! 그리고 useParams 를 이용해 도시 이름을 가져와 커스텀훅에 인자로 넣어주었어요!

 const { cityName } = useParams();
  const { data, isLoading, isError } = useGetCard({ cityName });   
const { weather, main, clouds } = data || {}; 

 const getWeatherImg = (description) => {
    const weather = WEATHER_TYPE.find(
      (item) => item.description === description
    );
    return weather?.imgURL;
  };

이 함수는 날씨 description 에 해당하는 날씨 이미지를 WHEATHER_TYPE 에서 찾아 image URL 을 반환해 주는 친구!

 <St.CardContainer>
      {isLoading ? (
        <St.SkeletonCard></St.SkeletonCard>
      ) : isError ? (
        <>Not Found</>
      ) : (
        <St.Card>

조건부 렌더링으로 에러 처리와 스켈레톤 ui 처리를 해 주었어요!
<span>{clouds?.all}%</span>
옵셔널 체이닝을 통해 undefined 를 참조하지 않게 처리해주었습니다!

✅ week4/weather_forecast/src/components/Header.jsx
아까 위에서 useParams 로 cityName 을 가져왔는데요, 이건 Header 에서 search 버튼 클릭 시, 라우팅을 해주었기 때문에 가능했어요! 그 부분은 요렇게 구현되어 잇습니당

const navigate = useNavigate();

  const searchBtnClick = () => {
    cityName && navigate(`/${period}/${cityName}`);
  };

이 부분은 week/day 를 변경하는 handler 함수!

const handlePeriodChange = (e) => {
  const periodInput = e.target.value;
  setPeriod(periodInput);
  if (periodInput === "day" && cityName) {
    navigate(`/day/${cityName}`);
  } else if (periodInput === "week" && cityName) {
    navigate(`/week/${cityName}`);
  }
};

✅ week4/weather_forecast/src/components/PageLayout.jsx

export default function PageLayout({children}) {
  return (
    <div>
      <Header />
      <div>{children}</div>
    </div>
  )
}

전체 레이아웃은 이렇게 Header 가 공통이니까, PageLayout 에 헤더를 넣고, PageLayout 으로 Route 들을 감싸서 모든 페이지가 공통 레이아웃을 가지게 해주었어요!!

✅ week4/weather_forecast/src/hooks/useGetCard.jsx
이 부분은 custom hook 부분!

const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);

데이터 상태들을 정의해주고

const getCard = async () => {
   try {
     setIsLoading(true);
     setIsError(false);
     await new Promise(resolve => setTimeout(() => resolve(), 500));
     const response = await axios.get(
       `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${
         import.meta.env.VITE_APP_WEATHER
       }&units=metric`
     );
     setData(response.data);
   } catch (error) {
     console.log(error);
     setIsError(true);
   } finally {
     setIsLoading(false);
   }

 };

비동기 요청을 하는데, Loading 은 true 로 Error 는 false 로 시작해서, 에러가 캐치되면 setIsError 를 통해 true 로 변경해주었고, finally 를 이용해 요청이 완료되면 로딩은 false 로 바꾸어주었어요!

await new Promise(resolve => setTimeout(() => resolve(), 500));
그리고 로딩을 위해서 setTimeout 시간 동안 기다리는 잉여 Promise 를 만들어 주었습니다!

useEffect(() => {
    getCard();
  }, [cityName]);

cityName 이 바뀌면 비동기요청을 다시 해야 하기 때문에, 비동기 요청을 하는 함수와 전체 함수를 분리해서 작성해두었는데요! 이렇게 해도 되고 혹시 나중에 다른 파일에서 필요하다면? getCard 를 data, isLoading, isError 와 함께 담아서 return 해 주면 되어요!!


🥺 소요 시간, 어려웠던 점

  • 3h
  • ~ 부분에서 이래서 시간을 얼만큼 썼어요..
  • 실습 열심히 따라했어서 어렵지 않게 했던 것 같습니다 :) 서희 언니 최고!!

🌈 구현 결과물

도시명 검색 시 카드 렌더링, 도시명을 설정하지 않은 경로에 대한 에러

화면_기록_2023-05-12_오후_11_46_11_AdobeExpress

없는 도시명 검색 시 에러

화면_기록_2023-05-12_오후_11_46_11_AdobeExpress (1)

@borimong borimong changed the title [4주차 기본 과제] 보리몽의 기상 예보 [4주차 기본/심화 과제] 보리몽의 기상 예보 May 12, 2023
@borimong borimong changed the title [4주차 기본/심화 과제] 보리몽의 기상 예보 [4주차 기본/심화/생각 과제] 보리몽의 기상 예보 May 12, 2023
Copy link

@kwonET kwonET left a comment

Choose a reason for hiding this comment

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

js의 꼼꼼한 기능을 잘 사용했다는 생각이 들어 ~!
그리고 궁금한 게 주간/일간을 다르게 데이터를 받아와야해서 컴포넌트나 커스텀 훅을 다 분리한 것 같은데 !
이럴 때는 현수처럼 분리하는 게 나을지 아니면 한 파일로 관리하는 게 좋을지 궁금하다

수고했어 !!

const weather = WEATHER_TYPE.find(
(item) => item.description === description
);
return weather?.imgURL;
Copy link

Choose a reason for hiding this comment

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

옵셔널체이닝을 이렇게 활용하는 구나 ~!! 너무 꼼꼼해

return (
<St.CardContainer>
{isLoading ? (
<St.SkeletonCard></St.SkeletonCard>
Copy link

Choose a reason for hiding this comment

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

카드안에서 스켈레톤을 쓰니까 스타일드컴포넌트로 깔끔하게 구분해줄 수 있군 !!

Copy link
Member

Choose a reason for hiding this comment

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

으잉 한달 전의 나는 이런 생각을 전혀못했군.. 완전 똑똑해 난 기존 코드 똑같이 또 복붙하고 className추가해서 구현했는데..!!!

try {
setIsLoading(true);
setIsError(false);
await new Promise(resolve => setTimeout(() => resolve(), 500));
Copy link

Choose a reason for hiding this comment

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

이거 왜 resolve를 시간차를 두고 할당해줬는지 알 수 있을까?
서버에서 데이터 받아오는 시간인감?

Comment on lines +24 to +26
} finally {
setIsLoading(false);
}
Copy link

Choose a reason for hiding this comment

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

오호 ! 공통적으로 적용되어야하는 코드는 finally로 처리! 배우고 가요 ㅎㅎ

Copy link
Member

@simeunseo simeunseo left a comment

Choose a reason for hiding this comment

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

아 나도 현수처럼 완전 도움되는 리뷰 하고싶은데... 또 감탄사만 연발하다 온...ㅠ
커스텀훅을 사용하는 방식이 잘 이해가 안됐었는데
데이터를 불러올때 사용하면 너무너무 유용하다는 걸 알아갑니당!!!
고생 많았어 ✨

Comment on lines +3 to +13
export const pulseAnimation = keyframes`
0% {
background-color: #94a3b8;
}
50% {
background-color: #cbd5e1;
}
100% {
background-color: #94a3b8;
}
`
Copy link
Member

Choose a reason for hiding this comment

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

아니 스켈레톤 애니메이션 너무멋진데??? 🤩🤩🤩

CardContainer: styled.div`
display: flex;
justify-content: center;
background-color: ${theme.colors.orange};
Copy link
Member

Choose a reason for hiding this comment

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

아니 나 이거 맨날 ${({ theme }) => theme.colors.orange}; 이렇게 하느라 손가락 나가는줄알았는데 (스니펫도 왜인지 안먹음 ㅠ) 그냥 theme을 import하면 되는거엿..? 무쳤다진짜

return (
<St.CardContainer>
{isLoading ? (
<St.SkeletonCard></St.SkeletonCard>
Copy link
Member

Choose a reason for hiding this comment

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

으잉 한달 전의 나는 이런 생각을 전혀못했군.. 완전 똑똑해 난 기존 코드 똑같이 또 복붙하고 className추가해서 구현했는데..!!!

Comment on lines +37 to +40
<div>
<span>온도</span>
<span>{main?.temp}</span>
</div>
Copy link
Member

Choose a reason for hiding this comment

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

요 반복되는 div를 styled-component로 정의해줘도 좋았겠다!! _

Comment on lines +22 to +26
<St.SkeletonCard />
<St.SkeletonCard />
<St.SkeletonCard />
<St.SkeletonCard />
<St.SkeletonCard />
Copy link
Member

Choose a reason for hiding this comment

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

아니 이렇게 하면되는구나진챸 너무 깔끔해

<>Not Found</>
) : (
data
?.filter((item, index) => index % 8 === 0)
Copy link
Member

Choose a reason for hiding this comment

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

모듈러 연산 참고해갑니다잇!!

Comment on lines +33 to +61
.map(({ id, dt_txt, weather, main, clouds }) => {
return (
<St.Card key={id}>
<div>{dt_txt.slice(5, 10)}</div>
<img
src={getWeatherImg(weather?.[0].description)}
alt="날씨 이미지"
/>
<main>
<div>
<span>온도</span>
<span>{main?.temp}</span>
</div>
<div>
<span>체감 온도</span>
<span>{main?.feels_like}</span>
</div>
<div>
<span>최저/최고</span>
<span>
{main?.temp_min}/{main?.temp_max}
</span>
</div>
<div>
<span>구름</span>
<span>{clouds?.all}%</span>
</div>
</main>
</St.Card>
Copy link
Member

Choose a reason for hiding this comment

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

데이터를 custom hook으로 가져오니까 data 사용이 무척 깔끔해진거같다...

Comment on lines +31 to +35
const handleEnter = (e) => {
if(e.key === "Enter") {
searchBtnClick();
}
}
Copy link
Member

Choose a reason for hiding this comment

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

아놔 이거까진 생각못했는데 너무 섬세하자나...?
아 근데 이거 보고 내가 한거 다시 봤는데 이거 따로 안해줘도 엔터 치면 submit버튼이 눌리는거같은....????

Comment on lines +6 to +29
export default function useGetCards(props) {
const { cityName } = props;
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);

const getCards = async () => {
try {
setIsLoading(true);
setIsError(false);
await new Promise((resolve) => setTimeout(() => resolve(), 500));
const response =
await axios.get(`https://api.openweathermap.org/data/2.5/forecast?q=${cityName}&appid=${
import.meta.env.VITE_APP_WEATHER
}&units=metric
`);
setData(response.data.list);
} catch (error) {
console.log(error);
setIsError(true);
} finally {
setIsLoading(false);
}
};
Copy link
Member

Choose a reason for hiding this comment

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

useGetCard와 useGetCards를 따로 분리한게 무척 인상적이야!!

Comment on lines +1 to +10
import React from 'react'
import { Outlet } from 'react-router-dom'

export default function Week() {
return (
<div>
<Outlet />
</div>
)
}
Copy link
Member

Choose a reason for hiding this comment

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

보니깐 Day component랑 Week component랑 완전히 똑같이 생겼는데 따로 분리해서 써야하는 이유가 궁금하다...!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants