子组件渲染两次

时间:2020-06-12 12:56:02

标签: reactjs redux

我有react app。基本上分为三个部分搜索框,左窗格和右窗格。左窗格包含项目列表,右窗格显示有关所选项目的详细信息。当用户进行搜索时-匹配搜索词的项目会显示在左窗格中,并且左窗格的第一个元素会显示在右窗格中。

Search组件执行axios调用以从后端获取项目列表,并将其传递到左侧组件以进行渲染。现在的问题是,当用户单击搜索btn时,右窗格将呈现两次。

搜索组件

import React, { Component } from "react"
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
import PropTypes from "prop-types"
import "./Search.css"
import axios from "axios"
import { Alert, Spin } from "antd"
import { browserHistory, hashHistory } from "react-router"
import queryString from "query-string"
import "./popup.css"
import * as data from "../app_translation.json"
import { AutoComplete, Input, Icon } from "antd"
import { SearchOutlined } from "@ant-design/icons"
import * as autoCompleteActions from "../actions/autoCompleteAction"
import { isMobile } from "react-device-detect"
import Results from "../results/Results"
const search_text = data.en.search_text
const search_input = data.en.search_input
const search_btn = data.en.search_button
const banner_text = data.en.banner_text

class Search extends Component {
  constructor(props) {
    super(props)
    this.state = {
      searchTerm: "",
      srcdata: [],
      autoCompleteList: [],
    }
    this.handleSubmit = this.handleSubmit.bind(this)
    this._getData = this._getData.bind(this)
  }

  /*checking whether the minimum character length is greater than 0 to call the API*/
  _validate(query) {
    if (query.length > 0) {
      this._getData(query)
    }
  }

  /*API call to get the data and store the data in the list*/
  _getData(query) {
    this.setState({
      infoStatus: "loading",
    })
    const main = this
    const URL = `/search/datasets?searchTerm=${query}`
    axios
      .get(URL)
      .then((res) => {
        main.setState({
          infoStatus: "loaded",
        })
        const srcdata = res.data
        this.setState({ srcdata })
      })
      .catch(function () {
        main.setState({
          infoStatus: "error",
        })
      })
  }

  render() {
    const { infoStatus } = this.state
    var data = null

    if (infoStatus === "loaded" && this.state.srcdata.length === 0) {
      const getquery = queryString.parse(window.location.href)
      const query_url = Object.values(getquery)[0]

      data = (
        <div className="info-no-result" id="no-results">
          <div>Sorry, no results were found for '{query_url}'</div>
          <p>Search suggestions</p>
          <ul className="search-suggestions">
            <li className="check-spell">
              <span>Check your spelling</span>
            </li>
            <li className="check-keyword">
              <span>Try different, more general, or fewer keywords</span>
            </li>
          </ul>
        </div>
      )
    } else if (infoStatus === "loading") {
      data = <Spin id="spinner" size="large" tip="Loading..."></Spin>
    } else if (infoStatus === "error") {
      data = (
        <div className="info-error" id="error-message">
          Error while loading data. Try again later.
        </div>
      )
    } else if (infoStatus === "loaded") {
      data = <Results data={this.state.srcdata} />
    }

    return (
      <div>
        {isMobile ? this.mobileView() : this.desktopView()}
        <Alert description={banner_text} type="info" showIcon closable />
        <div>{data}</div>
      </div>
    )
  }

  mobileView() {
    return (
      <form id="form-search" onSubmit={this.handleSubmit} method="get">
        <div id="search-tab" role="main">
          <div className="find-section" id="search-bar" role="search">
            <div>
              <label className="search-label" htmlFor="input-text">
                {search_text}
              </label>
            </div>
            <div>
              <AutoComplete
                style={{
                  height: "1.56rem",
                }}
                value={this.state.searchTerm}
                onChange={(value) => this.onTodoChange(value)}
                options={this.state.autoCompleteList}
              >
                <Input prefix={<SearchOutlined />} placeholder={search_input} />
              </AutoComplete>
            </div>
            <button
              className="search-button selected focused"
              id="search-button-mobile"
              type="submit"
            >
              {search_btn}
            </button>
          </div>
        </div>
      </form>
    )
  }

  desktopView() {
    return (
      <form id="form-search" onSubmit={this.handleSubmit} method="get">
        <div id="search-tab" role="main">
          <div className="find-section" id="search-bar" role="search">
            <label className="search-label" htmlFor="input-text">
              {search_text}
            </label>
            <AutoComplete
              style={{
                width: "35%",
                height: "1.56rem",
              }}
              value={this.state.searchTerm}
              onChange={(value) => this.onTodoChange(value)}
              options={this.state.autoCompleteList}
            >
              <Input prefix={<SearchOutlined />} placeholder={search_input} />
            </AutoComplete>
            <button
              className="search-button selected focused"
              id="search-button"
              type="submit"
            >
              {search_btn}
            </button>
          </div>
        </div>
      </form>
    )
  }

  async onTodoChange(value) {
    this.setState({
      searchTerm: value,
    })
    var list = []
    var wordList = await this.props.autoCompleteActions.fetchAutoComplete(value)
    for (var l in wordList.autoCompleteList) {
      list.push({ value: wordList.autoCompleteList[l] })
    }
    this.setState({ autoCompleteList: list })
  }

  handleSubmit(event) {
    hashHistory.push("/search?q=" + this.state.searchTerm)
    // this._getData(this.state.searchTerm);
    this._validate(this.state.searchTerm)
    event.preventDefault()
  }

  componentDidMount() {
    const getquery = queryString.parse(window.location.href)
    const query_url = Object.values(getquery)[0]
    if (query_url != null) {
      this.setState({ searchTerm: query_url }, () => {
        if (this.state.searchTerm.length > 0) {
          this._getData(this.state.searchTerm)
        }
      })
    }
  }
}

Search.propTypes = {
  autoCompleteList: PropTypes.array,
}
function mapStateToProps(state) {
  return {
    autoCompleteList: state.autoCompleteList,
  }
}
function mapDispatchToProps(dispatch) {
  return {
    autoCompleteActions: bindActionCreators(autoCompleteActions, dispatch),
  }
}
export { Search as SearchUI } //for UI testing
export default connect(mapStateToProps, mapDispatchToProps)(Search)

左窗格

import React, { Component } from "react"
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
import * as selectDatasetIdAction from "../actions/selectDatasetIdAction"
import * as datasetDetailsAction from "../actions/datasetDetailsAction"
import { message, Spin, Col, Row, List, Avatar } from "antd"
import InfiniteScroll from "react-infinite-scroller"
import { isMobile } from "react-device-detect"
import Rightpane from "../rightpane/Rightpane"
import "./Results.css"

class Results extends Component {
  constructor(props) {
    super(props)
    this.state = {
      selectedIndex: 0,
      loading: false,
      hasMore: true,
      data: [],
      datasetDetailsLoaded: false,
    }
    this.handleClick = this.handleClick.bind(this)
    this.getUrl = this.getUrl.bind(this)
  }

  async componentDidMount() {
    this.handleClick(0)
  }

  async componentDidUpdate(prevProps) {
    if (this.props.data !== prevProps.data) {
      this.handleClick(0)
    }
  }

  handleClick = async (index) => {
    let datasetId = this.props.data[index].id
    this.setState({ selectedIndex: index })
    this.setState({ datasetDetailsLoaded: false })
    this.props.selectDatasetIdAction.selectDatasetId(datasetId)
    await this.props.datasetDetailsAction.fetchDatasetDetails(datasetId)
    this.setState({ datasetDetailsLoaded: true })
  }

  getUrl = (source) => {
    var url =
      "https://portal.azure.com/#@cernerprod.onmicrosoft.com/blade/Microsoft_Azure_DataLakeStore/WebHdfsFolderBlade/endpoint/cernerdatalake.azuredatalakestore.net/path/"
    var regex = /<(Y{4})|(M{2})|(D{2})\>/
    if (source.match(regex)) {
      var strip = source.split("<")
      var encode = strip[0]
      var encoded_string = encodeURIComponent(encode)
      var final = encoded_string.replace(/%2F$/, "")
      return url + final
    } else {
      var tin = encodeURIComponent(source)
      var tee = tin.replace(/%2F$/, "")
      return url + tee
    }
  }

  handleInfiniteOnLoad = () => {
    this.setState({
      loading: true,
    })
    if (this.props.data.length > 49) {
      message.warning("Infinite List loaded all")
      this.setState({
        hasMore: false,
        loading: false,
      })
      return
    }
    this.setState({
      loading: false,
    })
  }

  /* To select icon type based on the source type of item */

  getIconTypeFromItemSourceType = (sourceType) => {
    switch (sourceType) {
      case "Data Base":
        return "../images/icons/database.png"
      case "Data Lake":
        return "../images/icons/datalake.png"
      case "Api":
        return "../images/icons/api.png"
      case "Kafka":
        return "../images/icons/kafka.png"
      case "Snowflake":
        return "../images/icons/snowflake.png"
    }
  }

  render() {
    return (
      <div>
        <Row>
          <Col span={isMobile ? 24 : 8}>
            <div
              className="datalake-results-info"
              id="results-content"
              style={
                document.getElementsByClassName("ant-alert-description") !== null
                  ? { height: "46.8rem" }
                  : { height: "43rem" }
              }
            >
              <InfiniteScroll
                initialLoad={false}
                pageStart={0}
                loadMore={this.handleInfiniteOnLoad}
                hasMore={!this.state.loading && this.state.hasMore}
                useWindow={false}
              >
                <List
                  header={<p>{this.props.data.length} Dataset(s) Found</p>}
                  dataSource={this.props.data}
                  bordered={true}
                  renderItem={(item, index) => (
                    <div
                      onClick={() => {
                        this.handleClick(index)
                      }}
                    >
                      <List.Item
                        style={
                          index === this.state.selectedIndex
                            ? {
                                backgroundColor: "#E6F1F9",

                                borderBottom: "1px solid #d9d9d9",
                              }
                            : { borderBottom: "1px solid #d9d9d9" }
                        }
                        key={item.uniqueId}
                      >
                        <List.Item.Meta
                          avatar={
                            <Avatar
                              src={`${this.getIconTypeFromItemSourceType(
                                item.sourceType
                              )}`}
                              className="custom-icon-style"
                              shape="square"
                            />
                          }
                          title={item.name}
                          description={
                            <p>
                              {item.sourceType}
                              <br />
                              {item.lastScannedDate}
                            </p>
                          }
                        />
                      </List.Item>
                    </div>
                  )}
                >
                  {this.state.loading && this.state.hasMore && (
                    <div className="demo-loading-container">
                      <Spin />
                    </div>
                  )}
                </List>
              </InfiniteScroll>
            </div>
          </Col>

          {!isMobile && (
            <Col span={16}>
              {this.state.datasetDetailsLoaded === false ? (
                <div className="spin-wraper">
                  <Spin
                    size="default"
                    tip="Dataset details loading.."
                    delay={500}
                  ></Spin>
                </div>
              ) : (
                <Rightpane
                  datasetDetailsData={this.props.datasetDetailsData}
                  userDetails={this.props.userDetails}
                  datasetDetailsLoaded={this.state.datasetDetailsLoaded}
                ></Rightpane>
              )}
            </Col>
          )}
        </Row>
      </div>
    )
  }
}

function mapStateToProps(state) {
  return {
    datasetDetailsData: state.datasetDetailsData,
    userDetails: state.userDetails,
    datasetId: state.selectedDatasetId,
  }
}

function mapDispatchToProps(dispatch) {
  return {
    datasetDetailsAction: bindActionCreators(datasetDetailsAction, dispatch),
    selectDatasetIdAction: bindActionCreators(selectDatasetIdAction, dispatch),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Results)

RightPane

import React from "react"
import { List } from "antd"
import ModalDisp from "../modal/ModalDisp"
import "./Rightpane.css"

class Rightpane extends React.Component {
  constructor(props) {
    super(props)
  }
  getLastAndFirstName = (assocId) => {
    const fallBackText = "No Owner Identified"
    if (this.props.userDetails === undefined || this.props.userDetails === null) {
      return fallBackText
    } else {
      const resultArray = this.props.userDetails.filter((associateDetail) => {
        return associateDetail.includes(assocId)
      })

      if (resultArray[0] === undefined || resultArray[0] === null) {
        return fallBackText
      } else {
        let assocArray = resultArray[0].split("/")
        return `${assocArray[0]} , ${assocArray[1]}`
      }
    }
  }

  render() {
    const {
      datasetDetails,
      source,
      sourceType,
      name,
    } = this.props.datasetDetailsData
    return (
      <div class="rightpane-wrapper">
        <List
          header={
            <div className="rightpane-common-style">
              <h2>
                Dataset Name{" "}
                <ModalDisp
                  datasetId={this.props.selectedDatasetId}
                  datasetDetailsData={this.props.datasetDetailsData}
                />
              </h2>
              <p>
                {datasetDetails === undefined || datasetDetails === null
                  ? "<DatasetTitle>"
                  : `${
                      datasetDetails.datasetTitle === undefined ||
                      datasetDetails.datasetTitle === null
                        ? name === undefined || name === null
                          ? "Dataset Title"
                          : name
                        : `${datasetDetails.datasetTitle}`
                    }`}
              </p>
            </div>
          }
        >
          <List.Item style={{ borderBottom: "solid 1px #d9d9d9" }}>
            <div className="rightpane-common-style source-container">
              <h3>Source</h3>
              <a className='anchor-style'>
                {source === undefined || source === null ? "source" : `${source}`}
              </a> 
            </div>
          </List.Item>
          <List.Item style={{ display: "block" }}>
            <div className="rightpane-common-style">
              <h3>Dataset Owner</h3>
              <p>
                {datasetDetails === undefined || datasetDetails === null
                  ? "No Owner Identified"
                  : `${
                      datasetDetails.ownerUsername === undefined ||
                      datasetDetails.ownerUsername === null
                        ? "No Owner Indentified"
                        : `${this.getLastAndFirstName(datasetDetails.ownerUsername)}
                        `
                    }`}
              </p>
            </div>
            <div className="rightpane-common-style source-style">
                  <span style = {{display : 'flex' , flexFlow : 'row'}}><h3>Source Type</h3>{" "}<p>
                {sourceType === undefined || sourceType === null
                  ? "source type"
                  : `${sourceType}`}
              </p></span>
              <p>
                {sourceType === undefined || sourceType === null
                  ? "source type"
                  : `${sourceType}`}
              </p>
            </div>
            <div className="rightpane-common-style">
              <h3>Dataset Description</h3>
              <p>
                {datasetDetails === undefined || datasetDetails === null
                  ? "data set description"
                  : `${
                      datasetDetails.datasetDescription === undefined ||
                      datasetDetails.datasetDescription === null
                        ? "No description is provided for this dataset"
                        : `${datasetDetails.datasetDescription}`
                    }`}
              </p>
            </div>
          </List.Item>
        </List>
      </div>
    )
  }
}
export default Rightpane

有人可以帮我解决这个问题吗?

0 个答案:

没有答案