7-6 讓使用者可以自行設定地區

本單元對應的專案分支為:select-location-in-weather-setting

單元核心#

這個單元的主要目標包含:

  • 讓使用者切換地區後,可以取得並顯示該地區的天氣資訊

在這個單元中要來讓使用這能夠實際更換顯示天氣資訊的地區,如下圖所示,使用者在選擇區域並點選儲存後,回到天氣資訊頁面就可以看到該地區即時的天氣資訊:

Imgur

整個流程會像這樣,在這整個專案中,大部分的資料狀態都是保存在最上層的 App 元件中,透過 props 的方式傳遞給其他需要的元件。因此這裡我們會維持這樣的原則,在 App 元件中,會保存要顯示的天氣資訊的地區,接著讓使用者可以在 WeatherSetting 頁面中去修改地區,但實際上是更新 App 中地區的資料狀態。

在 App 中定義目前地區#

由於實際上發送 API 請求,拉取資料的動作(useWeatherAPI)是在 App 元件中,因此可以在 App 元件中透過 useState 定義當前要拉取天氣資料的地區,並且把可以修改天氣地區的方法透過 props 傳到 WeatherSetting 元件中,讓該元件可以修改 WeatherApp 內當前地區的資料。

  1. 使用 useState 定義當前要拉取天氣資訊的地區(currentCity),預設值先定為「臺北市」
// ./src/App.js
// ...
const App = () => {
// STEP 1:定義 currentCity
const [currentCity, setCurrentCity] = useState('臺北市');
};
  1. 透過 import 匯入剛剛在 utils.js 中定義好的 findLocation 方法
  2. 根據 currentCity 來找出對應到不同 API 時使用的地區名稱,找到的地區取名為 currentLocation
// ./src/App.js
// STEP 2:匯入 findLocation
import { getMoment, findLocation } from './utils/helpers';
const App = () => {
const [currentCity, setCurrentCity] = useState('臺北市');
// STEP 3:找出每支 API 需要帶入的 locationName
const currentLocation = findLocation(currentCity);
};

透過 currentLocation 就可以取得所有 API 需要使用到的 locationName,以臺北市為例,currentLocation 會是:

{cityName: "臺北市", locationName: "臺北", sunriseCityName: "臺北市"} // currentLocation
  1. 這裡的 findLocation 這行也同樣可以用上 useMemo 的概念,只要 currentCity 沒有改變的情況下,即使元件重新轉譯,也不需要重新取值:
// ./src/App.js
const App = () => {
// STEP 4 使用 useMemo 把取得的資料保存下來
const currentLocation = useMemo(() => findLocation(currentCity), [
currentCity,
]);
};
  1. useWeatherAPIgetMoment 的參數中,就可以更改為使用 currentLocation 取得的地區資料:
// ./src/App.js
// ...
const App = () => {
// ...
const currentLocation = useMemo(() => findLocation(currentCity), [
currentCity,
]);
// STEP 5:再透過解構賦值取出 currentLocation 的資料
const { cityName, locationName, sunriseCityName } = currentLocation;
// STEP 6:在 getMoment 的參數中換成 sunriseCityName
const moment = useMemo(() => getMoment(sunriseCityName), [sunriseCityName]);
// STEP 7:在 useWeatherAPI 中的參數改成 locationName 和 cityName
const [weatherElement, fetchData] = useWeatherAPI({
locationName,
cityName,
authorizationKey: AUTHORIZATION_KEY,
});
//...
};
  1. 在 App 元件中原本使用的 LOCATION_NAMELOCATION_NAME_FORECAST 這兩個常數了,因為可以透過 currentLocation 取得,因此可以把這兩個常數移除。

透過 React Developer Tools 改變 currentLocation 的資料狀態#

現在我們可以透過 React Developer Tools ,從開發者工具中 State 的地方,把「臺北市」改成其他縣市,看看左側的畫面是否會連帶改變:

Imgur

這裡你會發現天氣卡片的地區顯示有些不太正確,這是因為原本的 weatherElement.locationName 會是局屬氣象站的名稱,而不是縣市名稱。

App 元件#

我們可以把 cityName 透過 props 傳入 WeatherCard 元件加以顯示:

// ./src/App.js
const App = () => {
// ...
// 將 cityName 傳入 WeatherCard 元件中
<WeatherCard
cityName={cityName}
weatherElement={weatherElement}
moment={moment}
fetchData={fetchData}
handleCurrentPageChange={handleCurrentPageChange}
/>;
};

WeatherCard 元件#

接著在 WeatherCard 元件中,可以直接把 cityName 從 props 取出後顯示。另外現在用不到原本從 weatherElement 中取出的locationName(局屬氣象站),可以一併移除:

// ./src/views/WeatherCard.js
// ...
const WeatherCard = ({
weatherElement,
moment,
fetchData,
handleCurrentPageChange,
cityName, // 取得 App 傳入的 cityName
}) => {
// ...
return (
// ...
// 在 JSX 中顯示 cityName
<Location>{cityName}</Location>
);
};

現在天氣卡片就會正確顯示縣市名稱,而不是局屬氣象站的名稱!

讓使用者從設定頁改變地區資訊#

接著,和切換頁面(handleCurrentPageChange)的方式一樣,把 handleCurrentCityChange 方法透過 props 傳入,讓 WeatherSetting 可以去修改 currentCity,以更新要拉取天氣資料的地區,如下圖所示:

圖一:

Imgur

App 元件#

// ./src/App.js
// ...
const App = () => {
const handleCurrentCityChange = (currentCity) => {
setCurrentCity(currentCity);
};
// ...
return (
// ...
// 將 cityName 和 handleCurrentCityChange 傳入 WeatherSetting 元件中
<WeatherSetting
cityName={cityName}
handleCurrentCityChange={handleCurrentCityChange}
handleCurrentPageChange={handleCurrentPageChange}
/>
);
};

WeatherSetting 元件#

  1. 從 props 中取出 cityNamehandleCurrentCityChange
  2. cityName 當成 locationName 這個 state 的預設值,因為 <input value={locationName}>,因此當使用者一進到此頁面時,地區的表單欄位就會是使用者當前的地區
// ./src/views/WeatherSetting.js
// ...
// 從 props 中取出 App 元件傳入的 cityName 和 handleCurrentCityChange
const WeatherSetting = ({
cityName,
handleCurrentCityChange,
handleCurrentPageChange,
}) => {
const [locationName, setLocationName] = useState(cityName); // 把 cityName 當成預設值
// ...
};
  1. 接著在原本的 handleSave 中,當使用者點擊儲存時,把使用者選擇的地區透過 handleCurrentCityChange 來更新 App 元件中 currentCity 的資料狀態,同時透過 handleCurrentPageChange 把使用者的頁面切換回天氣資訊頁(WeatherCard):
const WeatherSetting = ({
cityName,
handleCurrentCityChange,
handleCurrentPageChange,
}) => {
const [locationName, setLocationName] = useState(cityName); // 把 cityName 當成預設值
// ...
const handleSave = () => {
console.log(`儲存的地區資訊為:${locationName}`);
handleCurrentCityChange(locationName); // 更新 App 元件中的 currentCity 名稱
handleCurrentPageChange('WeatherCard'); // 切換回 WeatherCard 頁面
};
// ...
};

太棒了!現在你可以試著在設定頁面中切換地區,在按下儲存後,天氣卡片的所拉取地區也會連帶更改。

換你了!讓使用者可以自行設定地區#

在這個單元中,我們幾乎完成了所有「臺灣好天氣」的功能,現在使用者可以在設定頁面將地區切換成自己所在的地區,取得該地區的天氣資訊和降雨機率。現在要請你試著實作這個功能,你可以參考下面的步驟加以完成:

  • 在 App 元件中,透過 useState 取得 currentCitysetCurrentCity ,先以一個縣市(例如,高雄市)當作預設值。
  • 透過 findLocation 這個方法,將 currentCity 轉換成各個 API 對應需要使用的 locationName
  • findLocation 這個方法,一樣可以透過 useMemo 這個方法,減少不必要的重複運算
  • 取得各個 API 適用的 locationName 後,分別以參數的方式帶入 getMoment(得知目前是白天或晚上的方法)以及 useWeatherAPI (取得天氣資訊的 Custom Hook)
  • findLocation 後取得的 cityName 以 props 的方式傳入 WeatherCard 元件中,以顯示正確的縣市名稱
  • setCurrentCity 的方法先包成 handleCurrentCityChange 這個函式,再從 App 元件傳入 WeatherSetting 元件中,讓使用者可以在設定頁修改顯示的地區
  • 在 WeatherSetting 元件中,當使用者點擊儲存後,透過 handleCurrentCityChange 更新 App 元件中 currentCity 的資料狀態,並透過 handleCurrentPageChange 切換回天氣資訊頁

現在,你可以自由切換地區了!但現在還剩下最後一個功能需要完成,因為目前沒有把使用者選擇的地區資訊保存下來,因此只要使用者重新整理頁面,地區就會切換為一開始的 currentCity 的預設值。在下一個單元中,我們要來把使用者選擇的地區資訊保存下來。

本單元相關之網頁連結、完整程式碼與程式碼變更部分可於 select-location-in-weather-setting 分支檢視:https://github.com/pjchender/learn-react-from-hook-realtime-weather-app/tree/select-location-in-weather-setting

Imgur


圖一的文字內容:

App

  • handleCurrentCityChange
  • currentCity

WeatherSetting, WeatherCard

<WeatherSetting handleCurrentCityChange={handleCurrentCityChange} />, handleCurrentCityChange(“台南市”)

  1. 將更新 currentCity 的方法傳入子元件中
  2. 透過該方法更新 app 中的 currentCity 資料狀態