iT邦幫忙

2022 iThome 鐵人賽

DAY 9
3

Day9 自己做一個價值幾十萬的動態網站

第九課:單向資料傳遞useLocation與useNavigate實作與介紹,完成homeListPage與其props應用

前面一天我們完成了home Page的主體,包括feature到announcement Banner今天要來進入新的頁面hotelsListPage的UI製作

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與hotelsList的props應用

首先創造好兩個分頁後,我們想要讓他在homePage操作的searchBar資料,住在哪裡、什麼時間、幾個人等等的條件資料傳到hotelsList的listSearch,讓使用者可以按上searchBar上的搜尋按鈕跳轉至listSearch,並也一併將資料傳入

{useNavigate}與{Link}介紹與其概念

這邊就要提到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}都是可以在頁面上操作跳轉到不同分頁,但差別是一個會帶資料,一個就單純跳轉。


useNavigate用法與搭配巧思

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 上,來處理跳轉分頁與帶出資料

useLocation與useNavigate的搭配資料圖


在使用前可以先檢查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} 

反思單向資料傳遞的缺點 contextApi與redux等出現

從上面操作與useLocation與useNavigate的搭配資料圖可以看到,在資料傳遞上是useLocation與useNavigate是快速且單純的
,但如果遇到資料需要回傳時,再重複寫一次反向,顯然就有些不明智,所以較好解決方式應該是統一將需要傳遞的資料統一放在localStage就像紀錄使用者行為的瀏覽器暫存器,也就誕生了contextApi與redux等出現,且之後會詳細講解這部分


附上今天的github版本之二連結讓大家可以知道今天可以分兩天進行
bookingchallenge.day9正式結尾版.version

結論

程式碼有越來越長的趨勢在講解上怕拉太長,這邊會斟酌使用github連結與多更新版本,這樣就可以直接在github上看,再搭配圖文也許會比較好閱讀,後面的文章會斟酌看哪一種教學效果比較好,30天挑戰結束後打算去放假休息一下,放鬆自己被操壞的大腦跟眼睛,然後又發現10月多有其他的軟體競賽又感興趣了,又可以繼續燃燒自己的熱情GOGOGO。


上一篇
「全端挑戰」熱門產品排行製作、了解react-router-dom、props與 ` ? : ` 的搭配
下一篇
「全端挑戰」了解Css Grid介紹與應用,/:id 與params產品id分頁
系列文
自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言