Web_Advance
  • 本書簡介
  • Node.js 部分
    • Node 版本管理
    • 使用NPM
      • Yarn
    • 開始Node
    • Worker Thread
    • REPL
    • TCP
    • path
    • Cluster and Child_process
    • assert (自訂拋出的錯誤)
    • Stream(流)
    • util (工具類)
    • EventEmitter
    • fs 文件操作
    • Buffer
      • Binary Diff
      • 查看 Binary 檔案內容
    • Process (進程)
    • 錯誤處理
  • OS
  • Async Hook
  • TCP
  • HTTP
    • 有關爬蟲
    • HTTP/2
    • HTTP Protocal
  • HTTPS
    • HTTPS 流程
    • SSL pinning
    • HTTPS 封包解密
    • 建立自簽發 HTTPS 證書
    • 幫網站加上HTTPS
    • HTTPS原理
  • Crypto加密
  • 有關繼承
  • JS 基本
    • JavaScript 迴圈與異步處理
  • 使用 Express
    • 上傳檔案
    • 圖片伺服器
    • 簡單範例
  • 使用 Nest.js
  • 使用MongoDB
    • 設置帳戶登入權限
    • Mongoose 框架
    • 進階Mongo
    • 基本環境操作
    • MongoDB Sharding
  • 使用MySQL
    • Schema 架構設計
    • SQL 語法
    • SQL Procedure
    • Node.js 操作 MySQL
    • 使用 Sequelize
      • DB Migration
      • function
  • 使用PostgreSQL
    • 常見問題
    • replica
    • 基本指令
    • 使用Node.js操控pg
    • SQL 基礎
  • 使用TypeORM
  • RethinkDB
  • CSS 深度探討
    • Width, Height
  • React
    • 第三方組件
      • Formik
    • styled component
    • propTypes
    • React webpack 部署
    • React util
    • 寫component並且publish
    • create-react-app
    • Context API
    • i18n
    • Server side render
    • Next.js教學
    • Higher order component 與 Recompose
    • component 間 互相存取
    • React hook
  • React router
    • 自己寫一個Router
  • Redux
    • Redux Toolkit
    • 小技巧
    • Redux sagas
    • compose
  • React Native
    • adb
    • InApp Billing
    • Icon
    • SVG
    • Firebase
      • Phone Auth
    • 自動化測試
    • Splash screen
    • Websocket
    • Googla OAuth
      • iOS
      • Android
    • Facebook OAuth
      • iOS
      • Android
    • IOS
    • 第三方組件
      • Auth Code Input
      • Country Code Picker
      • onboarding screen
      • Toast
    • ESlint
    • Push notification
    • Android 上架步驟
    • Expo
    • router
      • react-navigation套件
    • 原生組件
      • RefreshControl
      • Modal
      • Alert
      • button
      • KeyboardAvoidingView
      • Drawer
      • Image
    • 限制螢幕垂直與水平
    • NativeBase UI
    • Debug
    • 常見問題
    • Network
    • 硬體操作
      • 隱藏鍵盤
      • 地理位置
      • 相機與圖片庫存取
    • Async Storage
    • Animation
    • Admob
  • JS 模組化
  • 使用 Webpack
  • 使用 Babel
  • JWT Token
  • ES6 ES7 ES8
    • Array method
    • ES8 Async
    • ES6 Proxy
    • ES6 Object
    • ES6 Arrow function
    • ES6 Promise
    • ES6 Symbol
    • ES6 Generator
    • ES6 Set,Map
    • ES6 Class
  • 模板引擎
    • Mustache
    • Handlebars.js
    • EJS
  • ESLint
  • 部屬到OpenShift
  • OpenStack
  • OAuth
    • Twitter OAuth
    • Google authenticator
    • facebook oauth
      • facebook like ,share
    • google oauth
  • Redis
  • 做一個簡單的markdown editor
  • Websocket
    • WebSocket 相關 Protocol
  • Sublime 安裝套件
  • Google api
    • Cloud Run
    • speech api
    • place autocomplete
    • Geocode
    • Map
      • React map
    • vision api
    • Google-recaptcha
    • Google sheet
  • Instagram API
  • Markdown 與 code pretty js
  • HTML5
    • IntersectionObserver
    • HTML5 audio
    • HTML5 Video 與 WebRTC
      • WebRTC 進階
      • WebEX API
    • HTML5 IndexedDB
  • Google Cloud Platform (GCP)
    • Cloud Storage
    • Cloud storage 串接 Cloud CDN
  • Vim 編輯器
  • 使用nginx
    • config
  • Unix 實用指令
    • 新 VPS 安裝流程
    • Ubuntu 22 安裝
    • Shell Script 教學
  • Git 實用指令
    • Git hook
    • 加上 SSH-key 到 GitHub
    • GPG簽名
  • SSH 實用指令
  • 有關Fetch與axios與跨域請求
  • 圖片上傳相關
    • imgur API
  • JS 格式轉換
  • js trick
  • AWS
    • AWS EBS
    • AWS HTTPS 憑證
    • AWS Cloudfront、ELB、ACL
    • AWS Athena
    • AWS CloudWatch、SNS
    • AWS RDS
    • AWS lambda
      • 範例
      • 加上權限控管
    • AWS S3
    • AWS DynamoDB
      • 結合Lambda
    • 快速把 EC2 串上 AWS Cloudfront CDN
    • AWS 證照相關
  • 有關日期Date
  • VS code 編輯器
    • VSCode 外掛 Plugin
  • CI with Gitlab&Jenkins
  • API 測試
    • Postman
      • 設置 Postman 環境變數
    • API Blueprint
    • swagger
      • 註解寫在Code內生成swagger UI
  • Javascript 實用Lib
  • 遠端寫程式
  • Quicktime錄影注意事項
  • Web開發進階Bug
  • Web壓力測試
  • LineBot
  • PM2部署
  • i18n
  • VPN
  • Protocol Buffers
  • Docker教學
    • LXC LXD
    • Docker Compose
    • Docker 原理
    • Docker 指令
  • E2E Testing
    • Cypress
    • PlayWright
    • Puppeteer 與其他 UI 測試工具
  • Unit Test (Jest & enzyme)
    • React Testing Library
    • mocha
  • Cassandra
    • cluster
  • Distribute Web
    • Dat project
    • IPFS project
  • Cluster and Child_process
  • 打包應用程式
  • Java
    • 使用gradle結合docker
  • Debug 頁面
  • Proxy
  • Chrome extension
  • 消息系統
    • RabbitMQ
  • 金流串接
    • Paypal
    • spgateway智富通
    • Stripe 串接
  • 有關Log
  • 設定 feature flag
  • Azure
    • Face API
    • Image Analyze API
    • Azure Serverless
    • Cosmos DB
      • 使用 SDK
      • 以 RESTful 操作 DB
      • 一致性策略與 DB replicate
  • NodeBB 筆記
  • 瀏覽器快取與緩存(Etag, If-None-Match)
  • 瀏覽器快取與緩存(Expires, Last-modified, Cache-Control)
  • Node.js 第三方模組
    • OpenCV
  • Kubernetes
    • 本地測試 MiniKube
  • Ngrok 使用
  • Telegram MiniAPP 開發
  • Firebase 教學
  • 演算法筆記
  • 圖表
    • Echart
    • TradingView 圖表
    • D3
    • 熱力圖 heatmap
  • 後端緩存 Cache
  • 資料一致性
  • Web 安全機制
    • Cookie 與 LocalStorage
  • Vue
    • Element UI
    • Devtool
    • Vuex
    • Vue router
  • 相關網路協議
    • 網路 IP 基礎
    • Google Search 技巧
    • 網路診斷工具
    • IP
    • DNS
  • GitLab 與 Drone
  • SMTP、POP、IMAP
    • SendGrid
  • IPC
  • 串流服務
    • Twilio
    • Agora
  • 其他資源
  • GraphQL
  • Typescript
  • UI 相關資源
  • FFmpeg
  • Unity 遊戲開發筆記
  • Influx DB
  • Windows 相關
  • DALL·E 3
  • Coap
  • Slack API
  • 資訊安全
    • 破解 ZIP 密碼
Powered by GitBook
On this page
  • React menu
  • React util
  • 1.如有一個共用的Component
  • 2.使用Rich Editor
  • 使用Material UI
  • 使用 I18N
  • React hook with lodash Debounce

Was this helpful?

Edit on GitHub
  1. React

React util

React menu

採用遞迴做法把所有子清單都顯示出來

config.js

export default [
  {
    label: 'Foobar',
    children: [
      { label: 'Foo', parentLabel: 'Foobar' },
      { label: 'Bar', parentLabel: 'Foobar' },
    ],
  },
  {
    label: 'Joedoe',
    children: [
      {
        label: 'Joe',
        children: [{ label: 'Hello' }],
      },
      {
        label: 'Doe',
        children: [{ label: 'world!' }],
      },
    ],
  },
  { label: 'Logout' },
];

index.js

import React, { useState } from 'react';
import SubHeaderMenu from './SubHeaderMenu';
import menus from './config';

const SubHeader = () => {
  const [showSubHeader, setShowSubHeader] = useState(false);
  return (
    <div
      style={{ display: 'inline-flex' }}
      onFocus={() => setShowSubHeader(true)}
      onMouseOver={() => setShowSubHeader(true)}
    >
      <h1>Menus</h1>
      <div style={{ marginTop: '30px' }}>
        {showSubHeader && <SubHeaderMenu menus={menus} />}
      </div>
    </div>
  );
};

export default SubHeader;

SubHeaderMenu.js

import React, { useState } from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';

const Menu = styled.div`
  border: 1px solid grey;
  background-color: white;
  width: 200px;
  padding: 8px;
  overflow-y: hidden;
`;

const SubHeaderMenu = ({ menus }) => {
  const [showSubHeader, setShowSubHeader] = useState(false);
  const [nextChildren, setNextChildren] = useState({});
  const handleMenuClick = (e, label) => {
    e.stopPropagation();
    // eslint-disable-next-line no-alert
    alert(`Clicked at ${label}`);
  };
  const handleMouseOver = (children) => {
    setShowSubHeader(true);
    setNextChildren(children);
  };
  const handleMouseOut = () => {
    // TODO
    // setShowSubHeader(false);
    // setNextChildren({});
  };
  return (
    <div style={{ display: 'flex' }}>
      <div>
        {menus.map(({ label, children }) => (
          <div
            role='menuitem'
            tabIndex={0}
            key={label}
            onClick={(e) => handleMenuClick(e, label)}
            onKeyPress={(e) => handleMenuClick(e, label)}
            onMouseOut={handleMouseOut}
            onBlur={handleMouseOut}
            style={{ display: 'flex' }}
          >
            <Menu>
              {label}
              <span
                onMouseOver={() => handleMouseOver(children)}
                onFocus={() => handleMouseOver(children)}
                style={{ float: 'right' }}
              >
                {children ? '>' : ''}
              </span>
            </Menu>
          </div>
        ))}
      </div>
      {showSubHeader && <SubHeaderMenu menus={nextChildren} />}
    </div>
  );
};

export default SubHeaderMenu;

SubHeaderMenu.defaultProps = {
  menus: [{}]
}

SubHeaderMenu.propTypes = {
  menus: PropTypes.arrayOf(PropTypes.object)
}

React util

1.如有一個共用的Component

不要於component的style 寫上如

Object.assign(props.style,style.item)

這樣共用元素的頁面會互相覆蓋style 因為react不會重新render元素

2.使用Rich Editor

這裡使用Draft.js 做example

官方的usage只給了一般的editor

如要有上方按鈕需使用官方提供的richEditor範例

我們使用時會先把它包成一個component在做引入

1.先新增RichEditor.js

import React, { Component, PropTypes as T } from 'react';
import { connect } from 'react-redux';
import radium from 'radium';
import { Editor, EditorState, RichUtils } from 'draft-js';

class RichEditor extends Component {



  constructor(props) {
    super(props);
    this.state = { editorState: EditorState.createEmpty() };

    this.focus = () => this.refs.editor.focus();
    this.onChange = (editorState) => this.setState({ editorState });

    this.handleKeyCommand = (command) => this._handleKeyCommand(command);
    this.onTab = (e) => this._onTab(e);
    this.toggleBlockType = (type) => this._toggleBlockType(type);
    this.toggleInlineStyle = (style) => this._toggleInlineStyle(style);
  }

  _handleKeyCommand(command) {
    const { editorState } = this.state;
    const newState = RichUtils.handleKeyCommand(editorState, command);
    if (newState) {
      this.onChange(newState);
      return true;
    }
    return false;
  }

  _onTab(e) {
    const maxDepth = 4;
    this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth));
  }

  _toggleBlockType(blockType) {
    this.onChange(
      RichUtils.toggleBlockType(
        this.state.editorState,
        blockType
      )
    );
  }

  _toggleInlineStyle(inlineStyle) {
    this.onChange(
      RichUtils.toggleInlineStyle(
        this.state.editorState,
        inlineStyle
      )
    );
  }

  render() {
    const { editorState } = this.state;

    let className = 'RichEditor-editor';
    var contentState = editorState.getCurrentContent();
    if (!contentState.hasText()) {
      if (contentState.getBlockMap().first().getType() !== 'unstyled') {
        className += ' RichEditor-hidePlaceholder';
      }
    }

    return (
      <div className="RichEditor-root">
        <BlockStyleControls
          editorState={editorState}
          onToggle={this.toggleBlockType}
        />
        <InlineStyleControls
          editorState={editorState}
          onToggle={this.toggleInlineStyle}
        />
        <div className={className} onClick={this.focus}>
          <Editor
            blockStyleFn={getBlockStyle}
            customStyleMap={styleMap}
            editorState={editorState}
            handleKeyCommand={this.handleKeyCommand}
            onChange={this.onChange}
            onTab={this.onTab}
            placeholder="請輸入..."
            ref="editor"
            spellCheck={true}
          />
        </div>
      </div>
    );
  }
}

const styleMap = {
  CODE: {
    backgroundColor: 'rgba(0, 0, 0, 0.05)',
    fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
    fontSize: 16,
    padding: 2,
  },
};

function getBlockStyle(block) {
  switch (block.getType()) {
    case 'blockquote': return 'RichEditor-blockquote';
    default: return null;
  }
}

class StyleButton extends React.Component {
  constructor() {
    super();
    this.onToggle = (e) => {
      e.preventDefault();
      this.props.onToggle(this.props.style);
    };
  }

  render() {
    let className = 'RichEditor-styleButton';
    if (this.props.active) {
      className += ' RichEditor-activeButton';
    }

    return (
      <span className={className} onMouseDown={this.onToggle}>
        {this.props.label}
      </span>
    );
  }
}

const BLOCK_TYPES = [
  {label: 'H1', style: 'header-one'},
  {label: 'H2', style: 'header-two'},
  {label: 'H3', style: 'header-three'},
  {label: 'H4', style: 'header-four'},
  {label: 'H5', style: 'header-five'},
  {label: 'H6', style: 'header-six'},
  {label: 'Blockquote', style: 'blockquote'},
  {label: 'UL', style: 'unordered-list-item'},
  {label: 'OL', style: 'ordered-list-item'},
  {label: 'Code Block', style: 'code-block'},
];

const BlockStyleControls = (props) => {
  const {editorState} = props;
  const selection = editorState.getSelection();
  const blockType = editorState
    .getCurrentContent()
    .getBlockForKey(selection.getStartKey())
    .getType();

  return (
    <div className="RichEditor-controls">
      {BLOCK_TYPES.map((type) =>
        <StyleButton
          key={type.label}
          active={type.style === blockType}
          label={type.label}
          onToggle={props.onToggle}
          style={type.style}
        />
      )}
    </div>
  );
};

var INLINE_STYLES = [
  {label: 'Bold', style: 'BOLD'},
  {label: 'Italic', style: 'ITALIC'},
  {label: 'Underline', style: 'UNDERLINE'},
  {label: 'Monospace', style: 'CODE'},
];

const InlineStyleControls = (props) => {
  var currentStyle = props.editorState.getCurrentInlineStyle();
  return (
    <div className="RichEditor-controls">
      {INLINE_STYLES.map(type =>
        <StyleButton
          key={type.label}
          active={currentStyle.has(type.style)}
          label={type.label}
          onToggle={props.onToggle}
          style={type.style}
        />
      )}
    </div>
  );
};

RichEditor.propTypes = {
};

const mapStateToProps = (state) => ({
});

export default connect(mapStateToProps, {
})(radium(RichEditor));

2.添加兩份css

Draft.css

/**
 * Draft v0.9.0
 *
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
.DraftEditor-editorContainer,.DraftEditor-root,.public-DraftEditor-content{height:inherit;text-align:initial}.public-DraftEditor-content[contenteditable=true]{-webkit-user-modify:read-write-plaintext-only}.DraftEditor-root{position:relative}.DraftEditor-editorContainer{background-color:rgba(255,255,255,0);border-left:.1px solid transparent;position:relative;z-index:1}.public-DraftEditor-block{position:relative}.DraftEditor-alignLeft .public-DraftStyleDefault-block{text-align:left}.DraftEditor-alignLeft .public-DraftEditorPlaceholder-root{left:0;text-align:left}.DraftEditor-alignCenter .public-DraftStyleDefault-block{text-align:center}.DraftEditor-alignCenter .public-DraftEditorPlaceholder-root{margin:0 auto;text-align:center;width:100%}.DraftEditor-alignRight .public-DraftStyleDefault-block{text-align:right}.DraftEditor-alignRight .public-DraftEditorPlaceholder-root{right:0;text-align:right}.public-DraftEditorPlaceholder-root{color:#9197a3;position:absolute;z-index:0}.public-DraftEditorPlaceholder-hasFocus{color:#bdc1c9}.DraftEditorPlaceholder-hidden{display:none}.public-DraftStyleDefault-block{position:relative;white-space:pre-wrap}.public-DraftStyleDefault-ltr{direction:ltr;text-align:left}.public-DraftStyleDefault-rtl{direction:rtl;text-align:right}.public-DraftStyleDefault-listLTR{direction:ltr}.public-DraftStyleDefault-listRTL{direction:rtl}.public-DraftStyleDefault-ol,.public-DraftStyleDefault-ul{margin:16px 0;padding:0}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listLTR{margin-left:1.5em}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listRTL{margin-right:1.5em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listLTR{margin-left:3em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listRTL{margin-right:3em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listLTR{margin-left:4.5em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listRTL{margin-right:4.5em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listLTR{margin-left:6em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listRTL{margin-right:6em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listLTR{margin-left:7.5em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listRTL{margin-right:7.5em}.public-DraftStyleDefault-unorderedListItem{list-style-type:square;position:relative}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth0{list-style-type:disc}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth1{list-style-type:circle}.public-DraftStyleDefault-orderedListItem{list-style-type:none;position:relative}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listLTR:before{left:-36px;position:absolute;text-align:right;width:30px}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listRTL:before{position:absolute;right:-36px;text-align:left;width:30px}.public-DraftStyleDefault-orderedListItem:before{content:counter(ol0) ". ";counter-increment:ol0}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth1:before{content:counter(ol1) ". ";counter-increment:ol1}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth2:before{content:counter(ol2) ". ";counter-increment:ol2}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth3:before{content:counter(ol3) ". ";counter-increment:ol3}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth4:before{content:counter(ol4) ". ";counter-increment:ol4}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-reset{counter-reset:ol0}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-reset{counter-reset:ol1}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-reset{counter-reset:ol2}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-reset{counter-reset:ol3}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-reset{counter-reset:ol4}

RichEditor.css

.RichEditor-root {
  background: #fff;
  border: 1px solid #ddd;
  font-family: 'Georgia', serif;
  font-size: 14px;
  padding: 15px;
}

.RichEditor-editor {
  border-top: 1px solid #ddd;
  cursor: text;
  font-size: 16px;
  margin-top: 10px;
}

.RichEditor-editor .public-DraftEditorPlaceholder-root,
.RichEditor-editor .public-DraftEditor-content {
  margin: 0 -15px -15px;
  padding: 15px;
}

.RichEditor-editor .public-DraftEditor-content {
  min-height: 100px;
}

.RichEditor-hidePlaceholder .public-DraftEditorPlaceholder-root {
  display: none;
}

.RichEditor-editor .RichEditor-blockquote {
  border-left: 5px solid #eee;
  color: #666;
  font-family: 'Hoefler Text', 'Georgia', serif;
  font-style: italic;
  margin: 16px 0;
  padding: 10px 20px;
}

.RichEditor-editor .public-DraftStyleDefault-pre {
  background-color: rgba(0, 0, 0, 0.05);
  font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
  font-size: 16px;
  padding: 20px;
}

.RichEditor-controls {
  font-family: 'Helvetica', sans-serif;
  font-size: 14px;
  margin-bottom: 5px;
  user-select: none;
}

.RichEditor-styleButton {
  color: #999;
  cursor: pointer;
  margin-right: 16px;
  padding: 2px 0;
  display: inline-block;
}

.RichEditor-activeButton {
  color: #5890ff;
}

之後引入上面包好的Component

即可看到如下畫面

接著因為我們要將其存成immutable的markdown轉為html,須使用如下模組

  constructor(props) {
    super(props);
    this.state = { editorState: EditorState.createEmpty() };

    this.onChange = (editorState) => {
      this.setState({ editorState });
      console.log(editorState.getCurrentContent())
      let html = stateToHTML(editorState.getCurrentContent());
      console.log(html)
    }

      //TODO 把此html傳給parent state使用即可

如上使用即可

使用Material UI

為原本的Materialize css包成的套件。

使用 I18N

他是i18next的react版本,nodejs也有類似,且用法相同,可共用程式。

React hook with lodash Debounce

const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);

e.g.

import React, { useState, useCallback } from 'react';
import classnames from 'classnames';
// you should import `lodash` as a whole module
import _ from 'lodash';
import axios from 'axios';

const ITEMS_API_URL = 'https://example.com/api/items';
const DEBOUNCE_DELAY = 500;

// the exported component can be either a function or a class

export default function Autocomplete({ onSelectItem }) {
  const [inputValue, setInputValue] = useState();
  const [listValues, setListValues] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const initListValue = () => {
    setListValues([]);
  }
  const fetchList = async (value) => {
    const { data } = await axios.get(`${ITEMS_API_URL}?q=${value}`);
    return data
  }
  const handleInputChange = async (e) => {
      debounce(e.target.value);
  }
  const debounce = useCallback(_.debounce(async newValue => {
      console.log(newValue)
      try {
        setIsLoading(true);
          initListValue();
          const value = newValue
          if(!value) {
              setIsLoading(false);
              return 
          }
          const data = await fetchList(value);
          setListValues(data);
          setIsLoading(false);
      } catch(err) {
          console.error(err);
      } 
  }, 500), []);
  return (
    <div className="wrapper">
      <div className={classnames("control", isLoading && 'is-loading')}>
        <input onChange={handleInputChange} type="text" className="input" />
      </div>
      {
          listValues.length > 0 && (
            <div className="list is-hoverable">
                {listValues.map(item => (
                    <a onClick={() => onSelectItem(item)} class="list-item" key={item}>{item}</a>
                ))}
            </div>
          )
      }
    </div>
  );
}
PreviousReact webpack 部署Next寫component並且publish

Last updated 4 years ago

Was this helpful?

ICON:

個人推薦:

而 需要且defineMessage與defaultMessage較為繁瑣。

https://github.com/facebook/draft-js/blob/master/examples/rich/rich.html
https://github.com/sstur/draft-js-export-html
http://www.material-ui.com/#/components/list
https://material.io/icons/#ic_swap_horiz
https://react.i18next.com/guides/quick-start
https://github.com/formatjs/react-intl