单击第一个按钮后 React 组件不更新

React Component Doesn't Update After First Button Click

我的代码生成一个输入字段,允许用户输入要搜索的值。然后,当他们单击“提交”按钮时,它会导致 displayMap 为真,因此当 MapDisplay 组件呈现时,它将通过 Map 组件触发 API 搜索,然后 return 值将显示在地图。

问题是这个过程只能运行一次。当我再次点击按钮时,它确实做了一些事情,我确认它正在输入框中获取新值,但我似乎无法弄清楚如何让地图再次呈现。

我试过在 this.setState 中设置其他变量,试图让它知道它需要再次渲染组件,但我想我遗漏了一些东西,因为没有任何效果。

我是 React 的新手,所以如果您能提供任何帮助,我将不胜感激。

这是 MainSearchBar.js,上面描述的大部分工作都在这里进行:

import Map from './newMap.js';

function MapDisplay(props) {
  if (props.displayMap) {
    return <Map toSearch = {props.searchTerm}></Map>;
  } else {
    return "";
  }
}

class MainSearchBar extends React.Component {

    constructor(props) {
      super(props);
      this.state = {
        displayMap: false,
        value: '',
        searchTerm: '',
        isOpened: false
      };
      //this.handleClick = this.handleClick.bind(this);
      this.handleChange = this.handleChange.bind(this);
    }

    handleClick = () => {
        this.setState({

          displayMap: true,
          isOpened: !this.state.isOpened,
          searchTerm: this.state.value
          });
        console.log(this.state.value);
      }

    handleChange(event) {
      this.setState({value: event.target.value});

    }

    render() {
      const displayMap = this.state.displayMap;
         return (
            <div class="homepage-search-bar">
              <input 
                type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
              </input>
              <button onClick={this.handleClick}>Search</button>
              <MapDisplay displayMap={displayMap} searchTerm={this.state.value} />  
            </div>
         )
    }
}

export default MainSearchBar;

这是调用 MainSearchBar 的地方

import Top20Box from '../components/getTop20Comp2.js';
import Header from '../components/Header.js';
import MainIntro from '../components/MainIntro.js';
import MainSearchBar from '../components/MainSearchBar.js';
import MainCTA from '../components/MainCTA.js';
import Footer from '../components/Footer.js';

export default class Home extends Component { 
  state = { 
  }

  render () {                                   
      return (
        <React.Fragment>
              <Header>
              </Header>
              <MainIntro />
              <MainSearchBar />
              <div className="top20-text">
                Top 20 trending hashtags
              </div>
              <Top20Box />
              <MainCTA />
              <Footer />
         </React.Fragment>
      )
   }
}

这是地图组件本身,以备不时之需:

import React from 'react';
import ReactMapGL, {Marker, Popup} from 'react-map-gl';
import axios from 'axios';

//for the loading animation function
import FadeIn from "react-fade-in";
import Lottie from "react-lottie";
import * as loadingData from "../assets/loading.json";

var locationCoordinates = [];
var locationToSearch = "";
var returnedKeywordSearch = [];
var newArray = [];

const defaultOptions = {
  loop: true,
  autoplay: true,
  animationData: loadingData.default,
  rendererSettings: {
    preserveAspectRatio: "xMidYMid slice"
  }
};

export default class Map extends React.Component {

//sets components for the map, how big the box is and where the map is centered when it opens
  state = {
            viewport: {
              width: "75vw",
              height: "50vh",
              latitude: 40.4168,
              longitude: 3.7038,
              zoom: .5
            },
            tweetSpots: null, //data from the API
            selectedSpot: null,
            done: undefined, //for loading function
          };

  async componentDidMount() {
    //searches the api for the hashtag that the user entered

    await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
        returnedKeywordSearch = response.data;
      }) //if the api call returns an error, ignore it
      .catch(function(err) {
        return null;
      });

      //goes through the list of locations sent from the api above and finds the latitude/longitude for each
      var count = 0;
      while (count < returnedKeywordSearch.length) {
        locationToSearch = returnedKeywordSearch[count].location;
        if (locationToSearch !== undefined) {
          var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
          .catch(function(err) {
            return null;
          });

          if (locationList !== null) {
            if (Array.isArray(locationList.data.features) && locationList.data.features.length)  
             {
              locationCoordinates.push(locationList.data.features[0].center);
              if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
                  && locationList.data.features[0].center !== undefined)
                {newArray.push({
                            id: returnedKeywordSearch[count].id, 
                            createdAt: returnedKeywordSearch[count].createdAt,
                            text: returnedKeywordSearch[count].text,
                            name: returnedKeywordSearch[count].name,
                            location: returnedKeywordSearch[count].location,
                            coordinates: locationList.data.features[0].center
                });
                }
            } 
          }
        }

        count++;
      }
      this.setState({tweetSpots: newArray});
      this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
  }     
//is triggered when a marker on the map is hovered over
  setSelectedSpot = object => {
    this.setState({
     selectedSpot: object
    });
  };

//creates markers that display on the map, using location latitude and longitude
  loadMarkers = () => {
    return this.state.tweetSpots.map((item,index) => {
      return (
        <Marker
          key={index}
          latitude={item.coordinates[1]}
          longitude={item.coordinates[0]}
        >
          <img class="mapMarker"
            onMouseOver={() => {
              this.setSelectedSpot(item);
            }}
            src="/images/yellow7_dot.png" alt="" />
        </Marker>
      );
    });
  };

//closes popup when close is clicked
  closePopup = () => {
    this.setState({
      selectedSpot: null
    }); 
  };

 //renders map component and loading animation
  render() {
    return (
      <div className="App">
        <div className="map">
          {!this.state.done ? (
          <FadeIn>
            <div class="d-flex justify-content-center align-items-center">
              <Lottie options={defaultOptions} width={400} />
            </div>
          </FadeIn>
        ) : (
        <ReactMapGL  {...this.state.viewport} mapStyle="mapbox://styles/mapbox/outdoors-v11"
         onViewportChange={(viewport => this.setState({viewport}))} 
         mapboxApiAccessToken="pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg">

          {this.loadMarkers()}

          {this.state.selectedSpot !== null ? (
            <Popup
              key={this.state.selectedSpot.id}
              tipSize={5}
              latitude={this.state.selectedSpot.coordinates[1]}
              longitude={this.state.selectedSpot.coordinates[0]}
              closeButton={true}
              closeOnClick={false}
              onClose={this.closePopup}
            >
               <div className="mapPopup">
                 <div className="header"> Tweet </div>
                 <div className="content">
                   {" "}
                 <p>
                   <b>Name:</b> {this.state.selectedSpot.name}
                 </p>
                 <p>
                   <b>Tweet:</b> {this.state.selectedSpot.text}</p>
                   <p><a href={'https://www.twitter.com/user/status/' + this.state.selectedSpot.id}target="_blank" rel="noopener noreferrer">View Tweet in Twitter</a>
                  </p>
                 </div>

               </div>  
            </Popup>
          ) : null}

        </ReactMapGL>

        )}
        </div>
      </div>

      );
  }
}

更新:4/28,根据我收到的答案,我将 MainSearchBar.js 的渲染更新为如下所示:

render() {
      const displayMap = this.state.displayMap;
         return (
            <div class="homepage-search-bar">
              <input 
                type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
              </input>
              <button onClick={this.handleClick}>Search</button>

              {this.state.displayMap && <Map toSearch = {this.searchTerm}></Map>}


            </div>
         )
    }

再次单击该按钮时,MainSearchBar.js 的状态会更新,但功能组件 MapDisplay 不会更新,因此地图也不会更新。

有很多方法可以解决这个问题。看代码,好像MapDisplay做的不多,可以考虑换成条件渲染

MainSearchBar.js

    render() {
      const displayMap = this.state.displayMap;
         return (
            <div class="homepage-search-bar">
              <input 
                type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
              </input>
              <button onClick={this.handleClick}>Search</button>
              {this.state.displayMap && <Map toSearch = {props.searchTerm}></Map>}
            </div>
         )
    }

然后在您的 Map 组件中,添加一个 componentDidUpdate 生命周期方法来检测 prop 的更新,当 props 更新时,它与 componentDidMount 做同样的事情。

  async componentDidMount(prevProps) {
    if (props.toSearch != prevProps.toSearch) {
      await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
        returnedKeywordSearch = response.data;
      }) //if the api call returns an error, ignore it
      .catch(function(err) {
        return null;
      });

      //goes through the list of locations sent from the api above and finds the latitude/longitude for each
      var count = 0;
      while (count < returnedKeywordSearch.length) {
        locationToSearch = returnedKeywordSearch[count].location;
        if (locationToSearch !== undefined) {
          var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
          .catch(function(err) {
            return null;
          });

          if (locationList !== null) {
            if (Array.isArray(locationList.data.features) && locationList.data.features.length)  
             {
              locationCoordinates.push(locationList.data.features[0].center);
              if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
                  && locationList.data.features[0].center !== undefined)
                {newArray.push({
                            id: returnedKeywordSearch[count].id, 
                            createdAt: returnedKeywordSearch[count].createdAt,
                            text: returnedKeywordSearch[count].text,
                            name: returnedKeywordSearch[count].name,
                            location: returnedKeywordSearch[count].location,
                            coordinates: locationList.data.features[0].center
                });
                }
            } 
          }
        }

        count++;
      }
      this.setState({tweetSpots: newArray});
      this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
    }
  }    

@wxker 感谢您的帮助!你肯定让我指出了正确的方向。

我将 MainSearchBar.js 中的渲染改回原来的样子。

并且我在Map组件中添加了一个ComponentDidUpdate,如下:

async componentDidUpdate(prevProps) {

    //searches the api for the hashtag that the user entered
    if (this.props.toSearch !== prevProps.toSearch) {

然后就和原来的componentDidMount一样了