第九課:單向資料傳遞useLocation與useNavigate實作與介紹,完成homeListPage與其props應用
前面一天我們完成了home Page的主體,包括feature到announcement Banner今天要來進入新的頁面hotelsListPage的UI製作
分為搜尋區塊與搜尋結果,主要會介紹搜尋區塊與搜尋結果的UI製作,後續也會直接上傳完整更新版,完成結束後也可以直接去github下載,重點主要會放在下面的資料連動不同分頁,往後的UI設計解說部分也會開始減少,將中心放往資料串接的重點上!
hotelsListPage UI設置圖
hotelsListPage.div
<div>
<Navbar />
<div className="listContainer">
<div className="listWrapper">
<div className="listSearch">
搜尋區塊
</div>
<div className="listResult">
搜尋結果
</div>
</div>
</div>
</div>
與hotelsListPage.scss
.listContainer {
display: flex;
justify-content: center;
margin-top: 30px;
.listWrapper {
width: 100%;
max-width: 1024px;
gap: 20px;
display: flex;
}
}
並開始先以listSearch 設計為主
listSearch 搜尋區塊UI設置圖
listSearch.div的版型 1個searchTitle+4個listItem
<div className="listSearch">
<div className='searchTitle'>
搜尋
</div>
<div className="listItem">
<!-- 地址搜尋欄 -->
<label>目的地/住宿名稱:</label>
<input type="text" className="searchInput" placeholder='要去哪裡?'/>
</div>
<div className="listItem">
<!-- 日期 搜尋欄 -->
<label>入住/退房日期</label>
<span className='dates' >
這邊會放header的打開視窗calendar
</span>
</div>
<div className="listItem">
<!-- 最低價格 搜尋欄 -->
<div className="listItemLimitPrice">
<span className="limitTitle">每晚最低價格</span>
<input type="text" className='searchInput' />
</div>
<div className="listItemLimitPrice">
<!-- 最高價格 搜尋欄 -->
<span className="limitTitle">每晚最高價格</span>
<input type="text" className='searchInput' />
</div>
<!-- conditions 搜尋欄 -->
<div className="listItmConditions">
<span className="SearchText" />3 位成人 · 2 位小孩· 1 間房</span>
</div>
</div>
<div className="listItem">
<button className='searchbtn'>搜尋</button>
</div>
</div>
listSearch.scss
.listSearch {
background-color: #ffc489;
padding: 5px;
border-radius: 2px;
position: sticky;
height: max-content;
top: 10px;
.searchTitle {
color: black;
background-color: #fd8915c6;
padding: 10px 10px;
border-radius: 1px;
font-size: 20px;
}
}
.listItem {
width: 250px;//這個為拉寬整個搜尋欄的250px,上面都沒有設置寬度
margin-top: 20px;//每個都取出高度20px的間距
display: flex;
flex-direction: column;
font-size: 12px;
color: rgba(0, 0, 0, 0.7);//這個是label顏色讓他稍為不要那麼黑
.searchInput { //這邊配合上面只要是input輸入欄
//包括dates,conditions,limitPrice,destination都是input輸入欄
border: none;
padding: 10px 5px; //並幫他們設定統一樣式
border-radius: 1px;
}
.dates {//dates這邊是用div 來排版所以底色讓他跟input一樣都白色
background-color: #fff;
border: none;
border-radius: 2px;
}
.searchbtn {
padding: 10px;
border: none;
font-size: 20px;
color: #fff;
background-color: rgb(53, 164, 127);
cursor: pointer;
&:hover{
background-color: rgb(15, 130, 92);
}
}
}
中間有兩個比較複雜的listItem
一個是裝著dates的這邊,一個是裝著最高、最低價格與設定幾位大人、小孩..(conditions)的
一個是裝著dates的這邊,值的講述的是,為了讓這邊也能夠有像homePage一樣打開的特效,我們將header.jsx的 4個useState完整的複製過來並在這邊重複使用,2個打開視窗、2個儲存入住時間與選擇有幾位大人、小孩、房間
const [openConditions, setOpenConditions] = useState(false);
const [openCalendar, setOpenCalendar] = useState(false);
const [dates, setDates] = useState([
{
startDate: new Date(),
endDate: new Date(),
key: 'selection',
}
]);
const [conditions, setConditions] = useState(
{
adult: 1, //初始人數,房間數為一
children: 0, //可以不一定要有小孩
room: 1,
}
);
這邊如果覺得[conditions, setConditions]的加加減減函數太複雜,可以再拉出來一個subComponent來讓他專門處理開關跟並同步引入設置在home 與 hotelsList Page,像booking.com他們的官方網站,相關的日期選擇器都是自己客製化自己的風格,大家也可以先嘗試自己拉出來玩玩。
並要記得這邊也要import locales(改成中文) DateRange與formate
import { DateRange } from 'react-date-range'
import { format } from 'date-fns'
import * as locales from 'react-date-range/dist/locale';
這邊忘記可以去看前面的彈跳視窗教學
<div className="listItem">
<label>入住/退房日期 {format(dates[0].startDate, "MM/dd/yyyy")} - {format(dates[0].endDate, "MM/dd/yyyy")}</label>
<span className='dates' >
<div className="searchInput" onClick={() => setOpenCalendar(!openCalendar)} >入住時間 - 退房時間</div>
{openCalendar && <DateRange
editableDateInputs={true}
onChange={item => setDates([item.selection])}
moveRangeOnFirstSelection={false}
ranges={dates}
className="date"
minDate={new Date()}
locale={locales['zhTW']}
/>}
</span>
</div>
這邊UI有新多一個最低與最高價格,會用在之後node.api串接好後,來用這個進行價格的filter,大家可以先創好useState,我應該會在大串聯時再設置,booking.com官方還有許多排序,如果整個專案都完成包括後台也設置好,還有多餘的天數,會在一起完成更多的細節讓她更貼近,商業動態網站
listResult 搜尋結果UI設置圖
這邊將listResult的SearchItem拉出來創作新的一個component
這邊你有時候會發現跟github或是demo不太一樣,這邊比較簡潔,是因為後續都還有一直在加小東西讓他變精緻,都也會更新在github上,讓大家可以參考,這邊小細節也想讓第一個searchItem發亮,所以我從hotelsList那邊導入props{active},並之後如果這邊導入真實資料,map的第一個我也會讓他保持active的特效,到時候就是使用index==0的判讀來區分是否要加active
<SearchItem active="active"/> //代表這是第一個
<SearchItem />
<SearchItem />
<SearchItem />
<SearchItem />
<SearchItem />
</div>
search component.div github連結
search component.scss github連結
hotelsList.div github連結
hotelsList.scss github連結
恭喜你完成hotelsListPage
完整版home+hotelsList+login UI(補上偷偷加的小東西與之後會補充register頁面
bookingChallenge Day8~9.version連結
首先創造好兩個分頁後,我們想要讓他在homePage操作的searchBar資料,住在哪裡、什麼時間、幾個人等等的條件資料傳到hotelsList的listSearch,讓使用者可以按上searchBar上的搜尋按鈕跳轉至listSearch,並也一併將資料傳入
這邊就要提到useNavigate(); // react router dom /一個可以同時傳資料與跳轉分頁的libp,因為是想把HomePage的header的資料傳給hotelsList的listSearch,所以要在header中
import { useNavigate } from 'react-router-dom';
並這邊已經要分頁之間的互動了所以也要用到Link一個一樣也是react-router-dom的lib,讓我們可以不用更改打上面網址,去到不同的分頁可以直接用按鈕等操作跳轉。
import {Link} from "react-router-dom"
而{ useNavigate }與{Link}都是可以在頁面上操作跳轉到不同分頁,但差別是一個會帶資料,一個就單純跳轉。
Link比較好理解,進到useNavigate得部分要稍微思考一下,
import { useNavigate } from "react-router-dom";
下面是官方寫法
function SignupForm() {
let navigate = useNavigate();
async function handleSubmit(event) {
event.preventDefault();
await submitForm(event.target);
navigate("../success", { replace: true });
}
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}
useNavigate 不像 Link一樣可以直接div包裹這想要的區域,
以函數來想也蠻合理的,因為他需要順便帶走資料,所以函數的思維來說才
有跳轉動作+變數(資料或是直接說是Component裡面useState的data )這種情況發生,且多半這個點擊跳轉就像在填google表單一樣,點擊與資料紀錄會在一起,所以將再form的submit鍵上寫處理送出等函數,再將使函數裡叫上 navigate("/分頁",{資料})
所以我們這邊也會寫在我們的header "搜尋"btn 上,來處理跳轉分頁與帶出資料
在使用前可以先檢查header的資料是否正確是否每個資料都有妥善的傳達
console.log確認好都有後,就可以往下邁進先叫出useNavigate
通常她叫出來後也會自動import useNavigate
const navigate =useNavigate();
const handleSearchBarSubmit =()=>{
}
onClick={handleSearchBarSubmit}
並在handleSearchBarSubmit裡面如官方解釋一樣導入Navigate()
const handleSearchBarSubmit =()=>{
navigate("/hotelsList",{state:{destination,dates,conditions}})
}
這邊有個小細節navigate("分頁",{上傳的State資料})
state:{destination,dates,conditions}的state是navigate規定的用法不能自己亂命名,因為會導致HotelsList不知道那是useState的資料而接不到,比如說:
stateData:{destination,dates,conditions}這樣就不行:x:
利用useNavigate將資料傳遞與跳轉到hotelsList後,
hotelsLists那邊就需要有承接這個資料與分頁轉動的函數,所以就可以使用useLocation()來承接useNavigate的資料,使用方式跟useNavigate一樣,在hotelsList頁面上打上1,同樣也要記得import useLocation
const location = useLocation();
console.log(locationSearchBarData)
並配上console.log檢查有沒有傳入成功
傳成功後,就可以把location的資料叫出來,所以仔細觀看location的array,要叫出資料必須先如下locationSearchBarData.state
然後再分別叫出Dates,destination...
const [destination, setDestination] = useState(locationSearchBarData.state?.destination);
const [dates, setDates] = useState(locationSearchBarData.state?.dates);
const [conditions, setConditions] = useState(locationSearchBarData.state?.conditions);
placeholder={destination===""?'要去哪裡?':destination}
從上面操作與useLocation與useNavigate的搭配資料圖可以看到,在資料傳遞上是useLocation與useNavigate是快速且單純的
,但如果遇到資料需要回傳時,再重複寫一次反向,顯然就有些不明智,所以較好解決方式應該是統一將需要傳遞的資料統一放在localStage就像紀錄使用者行為的瀏覽器暫存器,也就誕生了contextApi與redux等出現,且之後會詳細講解這部分
附上今天的github版本之二連結讓大家可以知道今天可以分兩天進行
bookingchallenge.day9正式結尾版.version
程式碼有越來越長的趨勢在講解上怕拉太長,這邊會斟酌使用github連結與多更新版本,這樣就可以直接在github上看,再搭配圖文也許會比較好閱讀,後面的文章會斟酌看哪一種教學效果比較好,30天挑戰結束後打算去放假休息一下,放鬆自己被操壞的大腦跟眼睛,然後又發現10月多有其他的軟體競賽又感興趣了,又可以繼續燃燒自己的熱情GOGOGO。