我有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
有人可以帮我解决这个问题吗?