第六課:實作useState與完成在SearchBar的彈跳視窗上part2
如何利用useState打開Calendar與條件選擇視窗(選人、房間那些的)
首先要先了解onClick(C一定要大寫)onClick可以放在任何div、span或是button裡面等等的,通常會這樣寫
<button className='SearchBtn' onClick={handleSearch}>搜尋</button>
onClick可以代表網頁一個很重要的觸發因素,鼠標點擊後發生的事件
下列舉幾個常出現,跟容易犯的錯
上面再一次提到(!openCalendar)!表示使變數資料相反 從true變成false,false變成true的概念,代表點擊一次會讓useState裡面的openCalendar改變
這邊第二個用法也只是不會回報錯誤,但基本上完全沒用,因為我沒有宣告動作,所以啟動等於沒有動作,所以不會把useState這樣使用,會再額外寫一個函數比如說handleClick然後裡面在裝setOpenCalendar的方式,這樣才會有函數啟動的與設計的相關動作!
首先我們要導入的lib為 react-date-range
官方連接為下列
https://hypeserver.github.io/react-date-range/
我跳了一個其中最順眼的跟使用locales翻成中文
所以事先需要npm i react-date-range 跟 import 下面
import 'react-date-range/dist/styles.css'; // main css file
import 'react-date-range/dist/theme/default.css'; // theme css file
import * as locales from 'react-date-range/dist/locale';
//用它來叫出不同版本的語言翻譯,把日曆換成中文
import { DateRange } from 'react-date-range';
我都是依照官方的指示,不懂的可以去那邊更詳細的了解看看,這邊上面兩個必須css都必須要導入,是外掛的styling沒有import會很醜
這邊react-date-range 我挑選了{ DateRange }
那上面有提到要設定打開關起 先設置state
const [openCalendar, setOpenCalendar] = useState(false);
並在在SearchBarItem裡面分別放上
onClick={()=>setOpenCalendar(!openCalendar)} 因為我想要整個<div className="SearchBarItem">
都能onClick後開啟日曆,所以我將icon與span裡面都放上onClick函數
<div className="SearchBarItem">
<FontAwesomeIcon icon={faCalendar} onClick={()=>setOpenCalendar(!openCalendar)}/>
<span className="SearchText" onClick={()=>setOpenCalendar(!openCalendar)} >08/16/2022-08/16/2022</span>
{openCalendar && <DateRange />}
</div>
下列為onClick錯誤示範
<div className="SearchBarItem" onClick={()=>setOpenCalendar(!openCalendar)}/>
但這邊注意不能直接上述onClick函數放入SearchBarItem將這樣做,因為SearchBarItem有涵蓋到Calendar這樣點選日曆他就也會觸發就會關起來,會造成根本不能選的窘境。
這邊特別講解下列 && 用法,對一開始學的人會覺得陌生,可以想成And
如果openCalendar是true 那就帶出後面 如果是false後面 也就消失,有點if else的感覺
{openCalendar && <DateRange/>}
這邊React的if else除了上述,也可以使用 ? :
使用方式為
問號前面是條件 中間是如果true的return結果 冒號後面是如果false的return結果 eg:
{type ==="HotelsList" ? "container listMode" : "container"}
如果type是HotelsList 那return"container listMode"如果不是就return"container"
所以上述{openCalendar && } 也可改成{openCalendar ? : <></>} 的形式 但會難以閱讀,所以還是採取上面的 && 形式的用法
先輸入裡面的屬性,為了紀錄calendar裡面的日期資料,包含開始日期與結束日期,所以一樣設置state並這邊useState一開始的資料不像前面單純是boolean,而是設置一個array中的一個objcet,new Date()也是React date的用法,之後設置api會很常用到new Date()也會特別講解,簡單來說就是設定data type為日期
const [dates, setdates] = useState([
{
startDate: new Date(),
endDate: new Date(),
key: 'selection',
}
]);
打上裡面的屬性
<DateRange
editableDateInputs={true}//可以讓日期被選取並輸入等等的
onChange={item => setdate([item.selection])}
//onChange把紀錄到的改動都紀錄到state date 裡面我們暫存器就會有選好的日期範圍,等於是輸入到暫存器
//item.selection的概念就是讓他選擇上傳到key=selection的部分,因為
moveRangeOnFirstSelection={false}
className="calendar"//並記得classname scss styling導入
minDate={new Date()}
ranges={dates}//才可以選範圍並範圍更改會re-render useState的date等於這是個抓取date範圍並顯示在日曆上,等於是從暫存器輸入到日曆顯示上面
locale={locales['zhTW']}
//最後這邊就是語言版本使用繁體中文zhTW概念
//就可以用到上面的import * as locales from 'react-date-range/dist/locale';
/>
在配搭上scss得處理
.calendar{
position: absolute;//這邊一樣用絕對定位讓他超出container往下
top:56px;
border: 1px lightgray solid;
box-shadow: 0px 0px 15px lightgray;
}//box-shadow增加陰影與邊線
安裝插件date-fns讓資料能 以08/19/2022 - 08/20/2022方式顯示
先npm i date-fns
再
import format from 'date-fns/format';
下列為使用語法
{format(dates[0].startDate, "MM/dd/yyyy")} - {format(dates[0].endDate, "MM/dd/yyyy")}
完成就可以連動了
首先打上conditions div
<div className="ConditionsContainer">
<div className="condition">
成人
<div className="conditionCounter">
<button className="conditionCounterButton" >
-
</button>
<span className="number">1</span>
<button className="conditionCounterButton" >
+
</button>
</div>
</div>
<div className="condition">
<span>小孩
<p>0-17 歲</p>
</span>
<div className="conditionCounter">
<button className="conditionCounterButton" >
-
</button>
<span className="number">0</span>
<button className="conditionCounterButton" >
+
</button>
</div>
</div>
<div className="condition">
房間
<div className="conditionCounter">
<button className="conditionCounterButton" >
-
</button>
<span className="number">1</span>
<button className="conditionCounterButton" >
+
</button>
</div>
</div>
這邊特別設置conditionCounter把 減號btn 數字 加號btn用在一區塊
之後才都能都統一放右邊,完成後沒加入scss應該會長下圖
附上scss檔的部分
.ConditionsContainer {
position: absolute;//一樣絕對定位
top: 60px;
right: 0;
width: auto;
background-color: white;
box-shadow: 0px 0px 15px lightgray;
border-radius: 5px;
padding: 10px 40px;
cursor: default; //靜止他一靠近就變point
.condition {
display: flex;
align-items: center;
justify-content: space-between;
//這邊將文字(小孩)與conditionCounter 分開 一左一右
width: 400px;
height: 60px;
p {//小孩下面 的<p>表顯示歲數
font-size: 14px;
color: gray;
}
.conditionCounter {
display: flex;
width: 100px;
justify-content: space-between;
//這邊左右分開的是 減號btn 數字 加號btn
align-items: center;
//下上對齊
.conditionCounterButton{
width: 30px;
height: 30px;
border-radius: 2px;
color: var(--primary-color);
background-color: white;
border: 1px solid var(--primary-color);
cursor: pointer;
&:hover {
background-color: rgba(53, 164, 127, 0.1);
//我讓他滑鼠過去時會有淡淡的淺綠色
}
}
}
}
}
跟上面一樣先用設置state來控制open的開關並使用boolean的true和false來執行,跟紀錄condition各個數字的useState操作都如同上面dates一樣
const [openConditions, setOpenConditions] = useState(false);
const [conditions, setConditions] = useState(
{
adult: 1, //初始人數,房間數為一
children: 0, //可以不一定要有小孩:)
room: 1,
}
);
在利用onClick放在icon與span裡面
onClick={() => setOpenConditions(!openConditions)}
與設置 {openConditions && <></>}
{openConditions &&
<div className="ConditionsContainer">
....
</div>
}
就可以實現開關
並開始製作上下連動 一樣可以先抓
{conditions裡面的資料}利用{conditions.adult},{conditions.children}..
將原本的 3位成人 2位小孩 1間房間換掉
首先我們要透過點擊加號+ 減號- 來控制中間數量,所以創造一個函數
下面這個比較難 因為利用到f(x)=y函數替換 可讀性不高
const handleCounter = (name, sign) => {
//導入得變數name是判斷adult,children還是room哪一個要加減
//並用sign來判斷是加還是剪
//用setConditions進來 上傳condition的數量
setConditions(prev => {//previous的縮寫
return{//一定要三個點,這個代表可以累加
...prev,//adult: 1, [adult]:value,
[name]: sign==="increase" ? conditions[name]+1 : conditions[name]-1
} //判斷符號是+還是- 如果是increase就+1 不是就-1 這邊有用到不明顯的 ? : if else用法
//導入name 如果讓今天如果是adult增加就只會增加它得部分
})
}
下面的盡量講解其原理
這邊一樣再利用onClick來控制 加減並可以先把{conditions.adult}一樣導入在視窗中的classname=number那邊
onClick={() => handleCounter("adult","decrease")}
onClick={() => handleCounter("adult","increase")}
onClick={() => handleCounter("chilren","decrease")}
onClick={() => handleCounter("chilren","increase")}
onClick={() => handleCounter("room","decrease")}
onClick={() => handleCounter("room","increase")}
為了防止這個我們可以在減號btn裡面各別再加上下面那樣 只有減號 因為他才會剪過頭
disabled={conditions.adult <=1 }
disabled={conditions.children <=0 }
disabled={conditions.room <=1 }
當btn disable就不能點,這邊我們也可以用css styling將它不能點的樣子,滑鼠鼠標變成禁止的在scss 的.conditionCounterButton上加上這一段
&:disabled{
cursor: not-allowed;
}
最後前面的搜尋地址也連動一下,剛好將前面兩個簡單
這邊跟前面不太一樣的是前面都是onClick,而這邊是input欄位所以要用onChange來上傳更新destination資料
先一樣注意自己有沒有先輸入useState
const [destination, setDestination] = useState("");
並往下找到第一個SearchBarItem 的input欄位"你想要去哪裡"
<input type="text" placeholder='你要去哪裡?' className='SearchInput' onChange={(e)=>setDestination(e.target.value)}/>
並在其中加上onChange={(e)=>setDestination(e.target.value)}
代表這這裡面的target.value會更新且上傳至setDestination的useState
最後可以在使用叫出console.log()主機瀏覽器檢查有沒有資料都上傳成功,這邊如果都有顯示就代表成功了!
所以到這邊恭喜你完成了Day6!
目前這篇應該會是內容最多的,就在準備這篇文章時有在想要不要在拆成兩篇,因為是製圖花上的時間比自己預期的還要還要長很多,不知道這麼詳細且白話的製作會不會有人看的懂,但保持著我這邊一開始常常會有的很多新手疑問,想著要如何深入淺出的讓人看得懂,如果有跑掉進度的人或是覺得這邊其實都太簡單了,可以從那邊開始實作,明天我們會繼續快速把首頁完成,也期許自己繼續加油!