用React刻自己的投资Dashboard Day12 - 下拉式选单筛选功能

tags: 2021铁人赛 React

还记得这个网站有筛选图表的功能吗?当初画wireframe的时候考量到未来图表可能会越来越多,因此规划了下拉式选单来做筛选功能,如下图:

本篇就来用React写写看这个筛选功能吧!

准备下拉式选单选项

先来看看"美国制造业电子零件订单"这个series,在FRED网站上是怎麽分类的,如下图:

可以看到下面有一些比较详细的资料,这边把它放大一下,里面包含几项内容:

  • Source:资料来源
  • Release:报告名称
  • Units:单位
  • Frequency:报告发布频率

这边稍微解释一下series、source、release三者的关系,series是指时间序列资料,例如"美国制造业电子零件订单"这个series是由过去每个月的数据组成,将这些数据依照时间排序就是一个序列,而一个series来自一个release,一个source通常有多个releases,一个release可能来自多个sources。

当要做筛选功能的时候,可以做的筛选方式可能就会有三种方式:

  • 透过source筛选
  • 透过release筛选
  • 同时筛选source及release

因此首先下拉式选单内要有可以选择的source与release,这些资料可以从FRED API取得,不过如果把API可以拿到的所有sources与releases放进来的话有点太多,因此我先依照目前有用到的series去列出有用到的sources与releases就好,制作成sources.json与releases.json如下:

src\data\sources.json

[
  {
    "id": 1,
    "realtime_start": "2021-08-22",
    "realtime_end": "2021-08-22",
    "name": "Board of Governors of the Federal Reserve System (US)",
    "link": "http://www.federalreserve.gov/"
  },
  {
    "id": 19,
    "realtime_start": "2021-08-22",
    "realtime_end": "2021-08-22",
    "name": "U.S. Census Bureau",
    "link": "http://www.census.gov/"
  }
]

src\data\releases.json

[
  {
    "id": 18,
    "realtime_start": "2021-08-22",
    "realtime_end": "2021-08-22",
    "name": "H.15 Selected Interest Rates",
    "press_release": true,
    "link": "http://www.federalreserve.gov/releases/h15/"
  },
  {
    "id": 20,
    "realtime_start": "2021-08-22",
    "realtime_end": "2021-08-22",
    "name": "H.4.1 Factors Affecting Reserve Balances",
    "press_release": true,
    "link": "http://www.federalreserve.gov/releases/h41/"
  },
  {
    "id": 95,
    "realtime_start": "2021-08-22",
    "realtime_end": "2021-08-22",
    "name": "Manufacturer's Shipments, Inventories, and Orders (M3) Survey",
    "press_release": true,
    "link": "http://www.census.gov/indicator/www/m3/"
  }
]

series新增来源编码

原本的series资料只有release_id去辨识,为了希望能用来源去筛选,因此要在series资料内新增source_id辨识来源:

src\data\chart-collections.json

[
  {
    "id": 1,
    "series_id": "TREAST",
    "release_id": 20,
    // 新增source id
    "source_id": 1,
    ...
  },
  {
    "id": 2,
    "series_id": "DGS10",
    "release_id": 95,
    // 新增source id
    "source_id": 1,
    ...
  },
  {
    "id": 3,
    "series_id": "A34HNO",
    "release_id": 18,
    // 新增source id
    "source_id": 19,
    ...
  }
]

制作下拉式选单

既然我们有了sources.json与releases.json,就可以把这些资料丢给React,这边有两个可以import资料的选择,一个是从最上层的App.js引入,另外一个是从选单元素Selector.js引入,考量到之後可能会在除了下拉式选单的地方用到这些资料,因此选择从App.js引入,再透过props传递到子元件。

从App引入资料,并透过props传递给Selector元件。

src\App.js

import Navbar from './components/Navbar/Navbar';
import Selector from './components/Selector/Selector';
import Charts from './components/Charts/Charts';
import chartCollections from './data/chart-collections.json';
// import 选单资料
import releases from './data/releases.json';
import sources from './data/sources.json';

function App() {
  return (
    <div className="App">
      <Navbar />
      <Selector
        releases={releases}
        sources={sources}
      />
      <Charts charts={chartCollections} />
    </div>
  );
}

export default App;

在Selector元件接收props,并使用list render制作下拉式选单的option。

src\components\Selector\Selector.js

import React from 'react';
import styles from './Selector.module.css';
import Form from 'react-bootstrap/Form';
import { Row, Col } from 'react-bootstrap';

const Selector = (props) => {
  return <Row className={styles.selector}>
    <Col sm={12} md={6} lg={4} className={styles.selectorItem}>
      <Form.Select
        aria-label="Default select example"
      >
        <option value={0}>All Sources</option>
        {props.sources.map((e) => (
          <option value={e.id} key={e.id}>{e.name}</option>
        ))}
      </Form.Select>
    </Col>
    <Col sm={12} md={6} lg={4} className={styles.selectorItem}>
      <Form.Select
        aria-label="Default select example"
      >
        <option value={0}>All Releases</option>
        {props.releases.map((e) => (
          <option value={e.id} key={e.id}>{e.name}</option>
        ))}
      </Form.Select>
    </Col>
  </Row>
};

export default Selector;

使用state储存选到的source_id及release_id

这次的下拉式选单功能,想做到让使用者选取项目後,不需要再按一个submit的按钮,下方的图表就会自动筛选。为了要做到这个功能,首先要让React知道使用者选了什麽选项,如果选项有改变,React也要马上启动筛选机制。

因此,我想到了使用state去追踪下拉式选单的状态,也就是让React记住当前选到的source_id及release_id,当使用者操作下拉式选单,就会去调整对应的state,再根据调制後的state去筛选图表。

建立State

建立filteredReleaseId与filteredSourceId两个State,并且透过props传递给Selector元件。

src\App.js

...

function App() {
  const [filteredReleaseId, setFilteredReleaseId] = useState(0);
  const [filteredSourceId, setFilteredSourceId] = useState(0);

  return (
    <div className="App">
      <Navbar />
      <Selector
        selectedReleaseId={filteredReleaseId}
        selectedSourceId={filteredSourceId}
        releases={releases}
        sources={sources}
      />
      <Charts charts={chartCollections} />
    </div>
  );
}

export default App;

Selector接收props

在Form.Select设定defaultValue为props传递进来的资料。

src\components\Selector\Selector.js

...

const Selector = (props) => {
  return <Row className={styles.selector}>
    <Col sm={12} md={6} lg={4} className={styles.selectorItem}>
      <Form.Select
        aria-label="Default select example"
        defaultValue={props.selectedSourceId}
      >
        <option value={0}>All Sources</option>
        {props.sources.map((e) => (
          <option value={e.id} key={e.id}>{e.name}</option>
        ))}
      </Form.Select>
    </Col>
    <Col sm={12} md={6} lg={4} className={styles.selectorItem}>
      <Form.Select
        aria-label="Default select example"
        defaultValue={props.selectedReleaseId}
      >
        <option value={0}>All Releases</option>
        {props.releases.map((e) => (
          <option value={e.id} key={e.id}>{e.name}</option>
        ))}
      </Form.Select>
    </Col>
  </Row>
};

export default Selector;

透过state筛选图表

可以透过filteredReleaseId与filteredSourceId去筛选图表资料,方式为:建立filteredCharts储存筛选後符合条件的资料,再将其传递到Charts元件。

src\App.js

...
function App() {
  const [filteredReleaseId, setFilteredReleaseId] = useState(0);
  const [filteredSourceId, setFilteredSourceId] = useState(0);
  
  // 筛选图表的方式
  const filteredCharts = chartCollections.filter(chart => {
    if (filteredReleaseId === 0 && filteredSourceId === 0) return true
    if (filteredReleaseId === 0 && filteredSourceId !== 0) {
      return chart.source_id === filteredSourceId
    }
    if (filteredReleaseId !== 0 && filteredSourceId === 0) {
      return chart.release_id === filteredReleaseId
    }
    if (filteredReleaseId !== 0 && filteredSourceId !== 0) {
      return chart.release_id === filteredReleaseId && chart.source_id === filteredSourceId
    }
  });

  return (
    <div className="App">
      <Navbar />
      <Selector
        selectedReleaseId={filteredReleaseId}
        selectedSourceId={filteredSourceId}
        releases={releases}
        sources={sources}
      />
      <Charts charts={filteredCharts} />
    </div>
  );
}

export default App;

下拉式选单新增事件,调用setState

上面的程序码知道如何筛选图表资料,接着,我们要让使用者透过下拉式选单去改变filteredReleaseId与filteredSourceId这两个state,就会促使React启动筛选的程序。
作法是建立两个setState函数,并将两个函数传递给Selector元件,让子元件可以呼叫函数修改父元件的state,这个过程称为资料的逆向传递。

src\App.js

...

function App() {
  const [filteredReleaseId, setFilteredReleaseId] = useState(0);
  const [filteredSourceId, setFilteredSourceId] = useState(0);

  const filteredCharts = chartCollections.filter(chart => {
    if (filteredReleaseId === 0 && filteredSourceId === 0) return true
    if (filteredReleaseId === 0 && filteredSourceId !== 0) {
      return chart.source_id === filteredSourceId
    }
    if (filteredReleaseId !== 0 && filteredSourceId === 0) {
      return chart.release_id === filteredReleaseId
    }
    if (filteredReleaseId !== 0 && filteredSourceId !== 0) {
      return chart.release_id === filteredReleaseId && chart.source_id === filteredSourceId
    }
  });
  
  // setState函数
  const releaseIdChangeHandler = (selectedReleaseId) => {
    setFilteredReleaseId(selectedReleaseId);
  };
  // setState函数
  const sourceIdChangeHandler = (selectedSourceId) => {
    setFilteredSourceId(selectedSourceId);
  };

  return (
    <div className="App">
      <Navbar />
      <Selector
        selectedReleaseId={filteredReleaseId}
        selectedSourceId={filteredSourceId}
        releases={releases}
        sources={sources}
        onReleaseIdChange={releaseIdChangeHandler}
        onSourceIdChange={sourceIdChangeHandler}
      />
      <Charts charts={filteredCharts} />
    </div>
  );
}

export default App;

在Selector的Form.Select元件内新增onChange事件,再透过父元件传递进来的事件将筛选到的值逆向传递回去。这边要注意的是子元件不能直接修改父元件的state,但是可以透过props将资料逆向传递回去,透过父元件的setState函数去修改父元件的state。

src\components\Selector\Selector.js

...

const Selector = (props) => {
  const releaseIdChange = (event) => {
    props.onReleaseIdChange(Number(event.target.value));
  };

  const sourceIdChange = (event) => {
    props.onSourceIdChange(Number(event.target.value));
  };

  return <Row className={styles.selector}>
    <Col sm={12} md={6} lg={4} className={styles.selectorItem}>
      <Form.Select
        aria-label="Default select example"
        defaultValue={props.selectedSourceId}
        onChange={sourceIdChange}
      >
        <option value={0}>All Sources</option>
        {props.sources.map((e) => (
          <option value={e.id} key={e.id}>{e.name}</option>
        ))}
      </Form.Select>
    </Col>
    <Col sm={12} md={6} lg={4} className={styles.selectorItem}>
      <Form.Select
        aria-label="Default select example"
        defaultValue={props.selectedReleaseId}
        onChange={releaseIdChange}
      >
        <option value={0}>All Releases</option>
        {props.releases.map((e) => (
          <option value={e.id} key={e.id}>{e.name}</option>
        ))}
      </Form.Select>
    </Col>
  </Row>
};

export default Selector;

小结

看似简单的筛选功能,其实也是蛮多程序码的,不过藉此也学到资料如何在父元件与子元件间传递,算是React内非常基本的知识。

虽然目前有了筛选功能,但是当图表越来越多张的时候,还是可能让画面变得很长,下一篇就来解决这个问题,最基本的方式应该就是用分页来处理。


<<:  Day.19 Dijkstra

>>:  网页颜色-30天学会HTML+CSS,制作精美网站

泳道图

要使用图来表达与非专业人员的执行程序与流程,除了一般的流程图就能做到之外,本题要介绍的这个图,是用角...

使用 KubeEye 为你的 K8s 集群安全保驾护航

使用 KubeEye 为你的 K8s 集群安全保驾护航 其他 2022-04-24 18:51:30...

虹语岚访仲夏夜-28(打杂的Allen篇)

我们开始往地下三楼移动。 「你就真的不好奇,那个Blue的状况吗?」 『好奇,然後呢? 然後能有什然...

从 IT 技术面细说 Search Console 的 27 组数字 KPI (1) 前言

在 5 年前,不只在开会时会这样要求,也常常说一个概念:SEO 是一种 Multi-Stack 的工...

【D21】修改食谱#2:根据市价,模拟小台改价

前言 昨天已经模拟出改价了,现在更进阶,使用小台的现价来改价。 参考网站:Futures 本日程序码...