iT邦幫忙

2022 iThome 鐵人賽

DAY 6
2

鐵人賽 Day6 自己做一個價值幾十萬的動態網站

第六課:實作useState與完成在SearchBar的彈跳視窗上part2

利用useState製作彈跳視窗,可應用在設定等多種想要打開的視窗

如何利用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的方式,這樣才會有函數啟動的與設計的相關動作!

製作Calendar彈跳視窗,導入Calendar lib

首先我們要導入的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這樣點選日曆他就也會觸發就會關起來,會造成根本不能選的窘境。

&&, ? : React的條件子句

這邊特別講解下列 && 用法,對一開始學的人會覺得陌生,可以想成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增加陰影與邊線

導入dates資料到上方搜尋欄資料,讓他連動

安裝插件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")}


完成就可以連動了

製作Conditon彈跳視窗,可以客製化選人、選房間

製作condition視窗UI圖div與scss styling


首先打上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);
                     //我讓他滑鼠過去時會有淡淡的淺綠色 
                }
            }
        }
    }
}

利用useState製作點擊conditions視窗產生連動效果

跟上面一樣先用設置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間房間換掉

製作函數來點擊加號減號做基本的useStateData運算

首先我們要透過點擊加號+ 減號- 來控制中間數量,所以創造一個函數
下面這個比較難 因為利用到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;
}


最後前面的搜尋地址也連動一下,剛好將前面兩個簡單

製作destination資料連動onChange用法

這邊跟前面不太一樣的是前面都是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!

結論

目前這篇應該會是內容最多的,就在準備這篇文章時有在想要不要在拆成兩篇,因為是製圖花上的時間比自己預期的還要還要長很多,不知道這麼詳細且白話的製作會不會有人看的懂,但保持著我這邊一開始常常會有的很多新手疑問,想著要如何深入淺出的讓人看得懂,如果有跑掉進度的人或是覺得這邊其實都太簡單了,可以從那邊開始實作,明天我們會繼續快速把首頁完成,也期許自己繼續加油!


上一篇
「全端挑戰」製作動態網站第一步從了解useState與它的用法開始
下一篇
「全端挑戰」React props、Array.map應用與feature資訊主體設置
系列文
自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言