개인 프로젝트 - 날씨 앱 (3) 특정 도시 날씨 (메인페이지구성)
개인 프로젝트 - 날씨 앱 (2) 현재 위치의 날씨 받아오기 (메인페이지구성)
개인 프로젝트 - 날씨 앱 (1) 현재 위치 받아오기날씨앱 메인페이지에서 구현 할 내용이, 내가 있는 위치의 날씨 정보 이므로, 현재 위치를 우선 알아야 한다.그 과정에 필요한 내용.날씨 앱 말고
creamymood.tistory.com
앞 게시글에서 이어집니다.
앞 게시글에서, 메인페이지에서의 현재 지역 위치 날씨 칸은 구현 완료 했다.
해당 게시글에서의 구현 목표는
1.(7)~(9)번 특정 나라의 온도 API를 받아온 뒤 렌더링 한다.
2. 예쁘지 않은 css..니까, 더 자주 쳐다보고 싶게 조금은 디자인적 요소를 추가해본다.
3. (11)번의 도시 검색창을 만들어준다.
1. 특정 나라의 온도 API를 받아온 뒤 렌더링
우선, OpenWeather 사이트에서, 특정 도시의 날씨 정보를 받아올 수 있었다.
두가지 방법이 있었는데 ❶ 도시 이름을 직접 넣어주기 ❷ 도시 ID 사용하기 (파일을 제공한다.).
우선 ❶번 방법으로, 테스트를 해본 뒤, 추후 검색 기능에서 ❷ 사용 해봐야겠다.
❶ 도시 이름으로 요청하기
위의 사진을 참고해서, api 호출을 하여 화면에 렌더링 해본다.
도쿄로 도시 이름에 넣어줘서 호출하니, 아래처럼 떴다.
코드는 다음과 같다.
//도시 이름으로, 날씨 호출하기 함수
const [tokyoWeather, setTokyoWeather] = useState(null)
const cityWeather = async() => {
const key = import.meta.env.VITE_WEATHER_KEY
const url = `https://api.openweathermap.org/data/2.5/weather?q=Tokyo&appid=${key}`
try{
const response = await fetch(url)
const data = await response.json()
console.log(data)
setTokyoWeather(data)
}
catch (error){
console.error('도시 날씨 데이터를 불러오지 못했습니다:', error);
}
}
//
useEffect(() => {
cityWeather()
}, []);
return (
{tokyoWeather ? (
<p>{`도쿄의 현재 날씨는 ${tokyoWeather.weather[0].description}`}</p>
) : (
<p>도쿄 날씨 정보를 불러오는 중...</p>
)}
)
2. 세 나라의 온도 API를 받아온 뒤 렌더링 (도시 공통 컴포넌트 방법)
나의 UI 목표는 3개의 나라를 구성 보여주는 것 이었으니까, 3개 다 이렇게 하나 하나 만들 수 있긴 하지만..
다른 블로그에서 배열로써 관리하는 방법을 참고 해서 한번 해보겠다.
원하는 나라 3개를 배열로써 만들어주고 map으로 돌려보겠음!
→ 해당 방식은 어떻게 우여곡절이 많아서 조교님이 추천해주신 방식으로 해보기로 했다
도시 공통 컴포넌트를 만들고, 배열의 도시를 프롭스(?매개변수)로 내려줘서, 배열을 맵돌리면
배열에 도시마다 도시 컴포넌트가 생기고 (예를 들어 : <City/> <City/> <City/>), 공통 컴포넌트 그 쪽에서 API를 부르는 방식으로!
2-1. 공통 도시 컴포넌트 만들기
도시 배열은
const cities = ['Tokyo', 'Busan', 'Toronto']
이렇게 만들어 두었고,
내부에 도시정보가 들어오면, API를 호출 할 수 있게 호출 함수 그대로 사용
function Cities ({city}) {
const [cityWeatherData, setCityWeatherData] = useState(null)
//도시 이름으로, 날씨 호출하기 함수
const cityWeather = async() => {
const key = import.meta.env.VITE_WEATHER_KEY
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}`
try{
const response = await fetch(url)
const data = await response.json()
console.log(data)
setCityWeatherData(data)
}
catch (error){
console.error('도시 날씨 데이터를 불러오지 못했습니다:', error);
}
}
useEffect(() => {
cityWeather()
}, []);
return(
<div>
{cityWeatherData? (
<>
<p>{`현재 위치는 : ${cityWeatherData.name}`}</p>
<p>{`현재 날씨는 : ${cityWeatherData.weather[0].description}`}</p>
</>
) : (
<p>날씨 정보를 불러오는 중...</p>
)}
</div>
)
}
export default Cities
2-2. Home.jsx에서 배열을 map으로 돌리기
그리고, home.jsx에서는
{cities.map((city,index)=>(
<Cities key={index} city={city}/>
))}
이렇게 배열을 맵으로 돌려서 <Cities /> <Cities /> <Cities /> 이렇게 세개가 뜰겁니다:)
간단했어!
디자인 적 요소를 조금은 가꿔야 할 것 같아서, 한번 다듬어 본다.
3. 특정 도시 날씨를 검색할 수 있는 검색창 만들기
도시 검색을 할 수 있는 검색창도 만들어줍니다.
3-1 Input창을 만들어서 버튼을 누르면 -> 디테일 페이지로 이동 하게 한다.
❶ return 안에, 이렇게 넣어줬고
<input placeholder="어느 도시가 궁금하세요?"/>
❷ 버튼을 달아서, detail로 넘어가게 했다.
그런데 버튼을 버튼 대신에, 리액트 라이브러리 중 하나인 아이콘을 사용해서 활용해봤다.
사용법은 다음과 같고,
<input placeholder="어느 도시가 궁금하세요?"/> <Link to ="/detail"><IoIosSearch /></Link>
난 링크를 달아줬다!
*추가 수정 + 다음 단계에서 수정 했습니다.
해당 부분은, 인풋창에 값으로 버튼을 눌렀을 때, 그 값으로 url을 이동하려고 하는거기 때문에..
Link가 아닌 Onclick으로 바꿔줬다.
설명은 아래와 같음
상황 정리
지금 하고 싶은 건:
사용자가 입력창에 도시 이름을 입력하면,
그 값(/detail/입력값)으로 이동하고 싶다.
이동은 버튼(아이콘)을 눌렀을 때 일어나야 한다.
Link를 쓰지 않고 navigate를 쓰는 이유
<Link>는 고정된 경로로 이동할 때 유용하다.
- 예: <Link to="/about">About</Link>
- 근데 사용자가 입력한 값에 따라 URL이 달라질 경우, <Link>의 to 속성에 동적으로 값 넣기 어렵거나 복잡해짐.
- 그래서 onClick으로 navigate()를 호출해서 동적으로 이동시키는 게 자연스러움.
코드 확인
const inputChange = (e) => {
setSearchInputValue(e.target.value)
}
const buttonChange = () => {
navigate(`/detail/${searchInputValue}`)
}
이건 아주 자연스러운 React 방식이야.
searchInputValue는 상태로 관리되고 있고,
navigate()는 react-router-dom의 훅이니까 페이지 전환에 딱 맞음.
추가 팁
혹시 사용자가 엔터를 눌러도 이동되게 하고 싶다면:
<input
placeholder="도시 이름을 영어로 입력해보세요!"
onChange={inputChange}
onKeyDown={(e) => {
if (e.key === 'Enter') buttonChange()
}}
/>
그런데 링크를 다니까 문제점...
하이퍼링크 처럼 아이콘의 색이 약간 바뀌었는데, 이걸 좀 고칠 수 있는 법은?
<Link>는 기본적으로 브라우저에서 하이퍼링크처럼 동작해서 파란색 텍스트 + 밑줄 스타일이 적용될 수 있다.
특히 <a> 태그로 렌더링되기 때문에 그렇다
아이콘에 그런 스타일이 적용되면 어색하니까, 스타일을 직접 없애주는 게 좋다.
해결법: Link에 스타일 초기화 주기
예를 들어:
import { Link } from 'react-router-dom';
import { Search } from 'lucide-react';
function MyComponent() {
return (
<Link to="/detail" style={{ textDecoration: 'none', color: 'inherit' }}>
<Search className="cursor-pointer" />
</Link>
);
}
💡 textDecoration: 'none' → 밑줄 제거
💡 color: 'inherit' → 부모 텍스트 색상 그대로 사용 (아이콘에도 영향 있음)
Tailwind CSS 쓸 경우
만약 Tailwind CSS를 쓰고 있다면 이렇게 쓸 수 있음:
<Link to="/detail" className="no-underline text-inherit">
<Search className="cursor-pointer" />
</Link>
만약 아이콘 색도 바뀌었다면?
<Link> 안의 아이콘이 파랗게 되었다면, 아마 부모 스타일 영향을 받는 것.
이럴 땐 아이콘에 color 스타일을 직접 지정해줘도 됌
<Search style={{ color: 'black' }} />
혹은 Tailwind에서:
<Search className="text-black" />
그러면 이렇게 검색 창도 만들어졌다.
다음 게시글에선,
1. 검색 창에서 특정 도시를 검색 한 뒤 버튼을 누르면 해당 값으로 api를 호출하고 -> 디테일을 표시해주는 작업을
2. 로딩 창을 구현
3. 추천 옷, 노래 컴포넌트 만들기
4. 한국어 지원으로 변경하기