iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Modern Web

用React刻自己的投資Dashboard系列 第 13

用React刻自己的投資Dashboard Day13 - 製作分頁(Pagination)功能

  • 分享至 

  • xImage
  •  
tags: 2021鐵人賽 React

一般的內容網站通常都會有將內容分頁或是動態讀取的功能,例如像是臉書的動態頁面,因為會不斷地有新的動態,也就是說它的資料量是會快速增加的,就很適合使用動態讀取(例如:scroll down)的功能;而像是博客來線上書店,新書發布的頻率就沒有臉書動態這麼高,因此系統可以在使用者進入網站的時候知道有多少資料需要呈現,就比較適合用分頁來做,因為大概可以算出有幾頁。

當然,上面說得只是一種分辨的標準,來判斷要使用動態讀取或是分頁功能,實際上除了資料量在一段時間內是否固定,還有很多其他面向需要考量,以這次的投資dashboard來說,因為我想要讓一頁最多就只有3張圖表,而我也能在使用者一進入網站,系統就知道需要多少頁面,因此我覺得分頁的方式蠻適合的。

分頁功能規劃

下圖表示資料的傳遞方向,原本沒有分頁功能元件,資料直接從Charts進入Card元件渲染出來,有了Pagination分頁元件後,就把它插在Charts與Card中間,做了分頁處理之後,才會顯示出來。

新增Pagination元件

由於我希望讓分頁元件是比較靈活的,因此蒐集了網路上各種分頁元件的寫法,綜合一版是我覺得功能比較符合我使用的,功能包含:可以設定每頁的資料數量、可以設定要顯示的分頁數、可以顯示目前是在哪一頁、可以有上一頁跟下一頁的功能。程式碼如下:

src\components\Selector\Selector.js

import React, { useState } from 'react';
import { Row } from 'react-bootstrap';
import styles from './pagination.module.css';

const Pagination = (props) => {
  // 從props導入的資料,包含每頁資料、要套入渲染的元件、顯示分頁數、每頁資料數
  const { data, RenderComponent, pageLimit, dataLimit } = props;
  // 總分頁數目
  const pages = Math.ceil(data.length / dataLimit);
  // 用一個state儲存目前在哪個頁面
  const [currentPage, setCurrentPage] = useState(1);
  // 下一頁
  const goToNextPage = () => {
    setCurrentPage((page) => page + 1);
  }
  // 上一頁
  const goToPreviousPage = () => {
    setCurrentPage((page) => page - 1);
  }
  // 跳至該頁面
  const changePage = (event) => {
    const pageNumber = Number(event.target.textContent);
    setCurrentPage(pageNumber);
  }
  // 載入當頁資料
  const getPaginatedData = () => {
    const startIndex = currentPage * dataLimit - dataLimit;
    const endIndex = startIndex + dataLimit;
    return data.slice(startIndex, endIndex);
  };
  // 計算目前分頁的數字是哪幾個分頁
  const getPaginationGroup = () => {
    let start = Math.floor((currentPage - 1) / pageLimit) * pageLimit;
    return new Array(pageLimit).fill().map((_, idx) => start + idx + 1);
  };

  return (
    <div>
      <div className="dataContainer">
        <Row>
          {getPaginatedData().map((d, idx) => (
            <RenderComponent key={idx} data={d} />
          ))}
        </Row>
      </div>
      <div className={styles.pagination}>
        <button
          onClick={goToPreviousPage}
          className={`${styles.prev} ${currentPage === 1 ? styles.disabled : ''}`}
        >
          prev
        </button>
        {getPaginationGroup().map((item, index) => (
          <button
            key={index}
            onClick={changePage}
            className={`${styles.paginationItem} ${currentPage === item ? styles.active : null}`}
          >
            <span>{item}</span>
          </button>
        ))}
        <button
          onClick={goToNextPage}
          className={`${styles.next} ${currentPage === pages || pages === 0 ? styles.disabled : ''}`}
        >
          next
        </button>
      </div>
    </div>
  );
};

export default Pagination;

CSS設定
src\UI\Pagination.module.css

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
}

.paginationItem {
  background: #fff;
  border: 2px solid #666;
  padding: 10px 15px;
  border-radius: 50%;
  height: 45px;
  width: 45px;
  position: relative;
  margin: 0 5px;
  cursor: pointer;
}

.paginationItem span {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.prev,
.next {
  background: #fff;
  border: none;
  padding: 10px;
  color: blue;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.4);
  margin: 0 10px;
  cursor: pointer;
}

.paginationItem.active {
  border: 1px solid #888;
  color: #888;
  pointer-events: none;
}

.prev.disabled,
.next.disabled {
  pointer-events: none;
  box-shadow: none;
  color: #999;
}

使用Pagination元件

在Charts元件中插入分頁元件並設定參數,這邊稍微解釋一下參數代表的意義:

  • data代表要渲染出來的資料
  • RenderComponent是要用來呈現資料的元件
  • dataLimit是每頁要呈現的資料數,在這邊就是只要呈現幾個Card元件
  • pageLimit是分頁數量設定,也就是在網頁最下方的分頁區塊,一次要顯示幾個分頁,我這邊的設定是用資料總數除以dataLimit設定的頁數再無條件進位,目的是要讓所有分頁一次呈現出來。

src\components\Charts\Charts.js

import React from 'react';
import Card from './Card';
import Pagination from '../../UI/Pagination';

const Charts = (props) => {
  return (
    <Pagination
      data={props.charts}
      RenderComponent={Card}
      pageLimit={Math.ceil(props.charts.length / 3)}
      dataLimit={3}
    />
  )
};

export default Charts;

修改Card元件的程式碼

做到這邊會發現無法正常運作,因為Pagination這個元件給子元件的名稱不符合之前的設定:

擷取一小段Paginaiton.js

<div className="dataContainer">
    <Row>
      {getPaginatedData().map((d, idx) => (
        <RenderComponent key={idx} data={d} />
      ))}
    </Row>
</div>

可以發現這邊是用data,所以子元件就要用props.data,因為這次用的子元件是Card,就把它拿出來看看,發現原本是寫props.item,只要把它都改成props.data即可:

src\components\Charts\Card.js

 import Highcharts from 'highcharts/highstock';
 import HighchartsReact from 'highcharts-react-official';
 import fredAPI from './fredAPI';
+import { Col } from 'react-bootstrap';

 const Card = (props) => {
   const [chartOption, setChartOption] = useState({
     title: {
-      text: props.item.title
+      text: props.data.title
    },
    xAxis: {
      type: "datetime",
      ...
    },
    series: [
       {
-       name: props.item.title
+       name: props.data.title
       }
     ]
   });
   
   ...
   
   useEffect(() => {
-    fetchData(props.item.series_id);
+    fetchData(props.data.series_id);
   }, [fetchData, props]);

   return (
-    <div className={styles.chartFrame}>
-      <HighchartsReact
-        highcharts={Highcharts}
-        constructorType={'stockChart'}
-        options={chartOption}
-      />
-      <div className={styles.chartInfo}>
-        <p className={styles.source}>source: {props.item.source}</p>
-        <p className={styles.date}>updated: {props.item.updated}</p>
+    <Col sm={12} md={12} lg={6} xxl={4} className={styles.chartItem} key={props.data.id}>
+      <div className={styles.chartFrame}>
+        <HighchartsReact
+          highcharts={Highcharts}
+          constructorType={'stockChart'}
+          options={chartOption}
+        />
+        <div className={styles.chartInfo}>
+          <p className={styles.source}>source: {props.data.source}</p>
+          <p className={styles.date}>updated: {props.data.updated}</p>
+        </div>
+        <div>
+          <p className={styles.document}>{props.data.document}</p>
+        </div>
       </div>
-      <div>
-        <p className={styles.document}>{props.item.document}</p>
-      </div>
-    </div>
+    </Col>

小結

到這邊大概就完成了,第一次寫分頁功能,還算是蠻快的,到目前就完成篩選跟分頁功能,接下來要來解決網頁在不同的分頁來回,會重複打API的問題,詳情見下篇。


上一篇
用React刻自己的投資Dashboard Day12 - 下拉式選單篩選功能
下一篇
用React刻自己的投資Dashboard Day14 - 解決重複發送API請求的問題
系列文
用React刻自己的投資Dashboard30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言