首页 > 解决方案 > 子组件未随更改重新渲染

问题描述

我有两个组件,它们都是同一个父组件的子组件,它们都呈现一个地点列表 - 一个将地点作为地图上的标记加载的地图,然后是一个带有过滤器菜单的地点列表网格。我想要做的是将过滤器点击从地点列表组件传递到地图组件以过滤标记。为了实现这一点,我在父组件中有一个名为 handlePlaceFilter() 的函数,我将其作为道具传递到列出子组件的位置。

在过滤器单击子项后,我可以从父组件触发控制台日志语句,并且可以将过滤的位置列表传递给父组件 - 但我无法让它重新渲染带有过滤列表的任何一个组件的地方。

这是包含子组件和 handlePlaceFilter() 函数的父组件:

import React from 'react';
import Header from './Header';
import MapContainer from './MapContainer';
import _ from 'lodash';
import Places from './Places';
const Cosmic = require('cosmicjs')();

export default class PlaceIndex extends React.Component {
    constructor (props) {
        super(props);
        this.handlePlaceFilter = this.handlePlaceFilter.bind(this);
        this.state = {
            destination: '',
            destinations: '',
            places: '',
            globals: '',
        }
    }

    async componentDidMount() {
        const bucket = Cosmic.bucket({
            slug: 'where-she-goes',
            read_key: '',
            write_key: ''
        });
        try {
            let result = await bucket.getBucket()
            this.setState (() => {
                return {
                    destination: _.find(result.bucket.objects, { slug: this.props.match.params.slug }),
                    destinations: _.filter(result.bucket.objects, {type_slug: 'destinations'}),
                    places: _.filter(result.bucket.objects, {type_slug: 'places'}),
                    globals: _.filter(result.bucket.objects, {type_slug: 'globals'})
                }
            });
        } catch (err) {
            console.log(err)
        }
    }

    handlePlaceFilter (places) {
        
        console.log("Place filter clicked!")
        console.log(places)
        this.setState (() => {
            return {
                places: places
            }
        });
    }

    render() {
        if (!this.state.places || !this.state.destination)
            return <p>Loading...</p>

        // compile list of destination plus children
        let placeDestinations = new Array();
        placeDestinations.push(this.state.destination.slug);
        this.state.destination.metadata.child_destinations &&
        this.state.destination.metadata.child_destinations.map(destination => {
            placeDestinations.push(destination.slug)
            destination.metadata.child_destinations &&
            destination.metadata.child_destinations.map(child_destination => {
                placeDestinations.push(child_destination.slug)
            })
        })
        console.log("Destination List")
        console.log(placeDestinations)

        // filter places by destination list

        let places = this.state.places.filter(function(place) {
            return placeDestinations.includes(place.metadata.destination.slug);
        })
        console.log("Places")
        console.log(places)

        let destinationCenter = {
            latitude: this.state.destination.metadata.latitude,
            longitude: this.state.destination.metadata.longitude
        }

        return (
            <div>
                <Header 
                    destinations={this.state.destinations}
                    globals={this.state.globals}
                />
                <div className="places-title text-center">
                    <h2>All Places in {this.state.destination.title}</h2>
                </div>
                <MapContainer 
                    places={places} 
                    center={destinationCenter}
                />
                <Places 
                    places={places}
                    handlePlaceFilter={this.handlePlaceFilter}
                />
            </div>
        );
    }
}

这是地方清单的子组件:

import React from 'react'
import _ from 'lodash'

export default class Places extends React.Component {
    constructor (props) {
        super(props);
        this.showHotels = this.showHotels.bind(this);
        this.showAll = this.showAll.bind(this);
        this.showRestaurants = this.showRestaurants.bind(this);

        let places = _.flatMap(this.props.places, this.props.places.metadata);
        var allplaces = new Array();
        var hotels = new Array();
        var restaurants = new Array();
        var sights = new Array();

        places &&
        places.map(place => {
            allplaces.push(place)
            if (place.metadata.place_type == 'Hotel') {
                hotels.push(place)
            }
            if (place.metadata.place_type == 'Restaurant') {
                restaurants.push(place)
            }
            if (place.metadata.place_type == 'Sight') {
                sights.push(place)
            }
        })

        // Limit # of places in each array to customize for page contect

        if (this.props.limit) {
            (allplaces.length > 0) ? (allplaces.length = this.props.limit) : allplaces;
            (hotels.length > 0) ? (hotels.length = this.props.limit) : hotels;
            (restaurants.length > 0) ? (restaurants.length = this.props.limit) : restaurants;
            (sights.length > 0) ? (sights.length = this.props.limit) : sights;
        }

        this.state = {
            current: "All",
            places: allplaces,
            hotels: hotels,
            restaurants: restaurants,
            sights: sights,
            allplaces: allplaces
        }
    }

    showAll (e) {
        e.preventDefault();
        this.props.handlePlaceFilter(this.state.allplaces);
        this.setState (() => {
            return {
                current: "All",
                places: this.state.allplaces
            }
        });
    }

    showHotels (e) {
        e.preventDefault();
        this.props.handlePlaceFilter(this.state.hotels);
        this.setState (() => {
            return {
                current: "Hotels",
                places: this.state.hotels
            }
        });
    }

    showRestaurants (e) {
        e.preventDefault();
        this.props.handlePlaceFilter(this.state.restaurants);
        this.setState (() => {
            return {
                current: "Restaurants",
                places: this.state.restaurants
            }
        });
    }

    showSights (e) {
        e.preventDefault();
        this.props.handlePlaceFilter(this.state.sights);
        this.setState (() => {
            return {
                current: "Sights",
                places: this.state.sights
            }
        });
    }

    render () {
        if (this.state.allplaces.length > 0) {
            return (
                <div className="container">
                    <div className="row">
                        <div className="col-md-12">
                            <div className="blogFilter text-center text-uppercase">
                                <ul className="list-inline">
                                    <li>{(this.state.current == "All") ? <a href="#" onClick={this.showAll} className="current">All</a> : <a href="#" onClick={this.showAll}>All</a>}</li>
                                    <li>{(this.state.hotels.length > 0) ? ((this.state.current == "Hotels") ? <a href="#" className="current"  onClick={this.showHotels}>Hotels</a> : <a href="#" onClick={this.showHotels}>Hotels</a>) : <span></span>}</li> 
                                    <li>{(this.state.restaurants.length > 0) ? ((this.state.current == "Restaurants") ? <a href="#" className="current"  onClick={this.showRestaurants}>Restaurants</a> : <a href="#" onClick={this.showRestaurants}>Restaurants</a>) : <span></span>}</li>
                                    <li>{(this.state.sights.length > 0) ? ((this.state.current == "Sights") ? <a href="#" className="current"  onClick={this.showSights}>Sights</a> : <a href="#" onClick={this.showSights}>Sights</a>) : <span></span>}</li>
                                </ul>
                            </div>
                            <div className="row">
                                <div className="blogContainer">
                                    {
                                        this.state.places &&
                                        this.state.places.map(place => {
                                            console.log("Places")
                                            console.log(place)
                                            return (
                                                <div className="col-sm-3 design">
                                                    <article className="portfolio portfolio-2 post-grid">
                                                        <div className="post-thumb">
                                                            <a href={`/place/${place.slug}`}><img src={place.metadata.hero.imgix_url} alt="" /></a>
                                                            <div className="post-thumb-overlay text-center">
                                                                <div className="text-uppercase text-center">
                                                                    <a href="single-portfolio.html"><i className="fa fa-link"></i></a>
                                                                    <a href={place.metadata.hero.imgix_url} ><i className="fa fa-search"></i></a>
                                                                </div>
                                                            </div>
                                                        </div>
                                                        <div className="post-content">
                                                            <header className="entry-header text-center text-uppercase">
                                                                <h6><a href={`/place/${place.slug}`}>{place.metadata.place_type}</a></h6>
                                                                <h2 className="entry-title"><a href=" ">{place.title}</a></h2>
                                                            </header>
                                                        </div>
                                                    </article>
                                                </div>
                                            )
                                        })
                                    }
                                </div>
                            </div>
                        </div>
                    </div>
                </div>    
            )
        } else {
            return (
                <div></div>
            )
        }

    }

}

这是 Map 的子组件:

import React, { Component } from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';

const mapStyles = {
  width: '100%',
  height: '300px'
};

let geocoder;

export class MapContainer extends Component {
  constructor (props) {
    super(props);
    this.onMarkerClick = this.onMarkerClick.bind(this);
    this.displayMarkers = this.displayMarkers.bind(this);
    let addresses = new Array();
    this.props.places &&
      this.props.places.map(place => {
        addresses.push(place.metadata.address)
    })
    this.state = {
        lat: this.props.center.latitude,
        lng: this.props.center.longitude,
        showingInfoWindow: false,
        activeMarker: {},
        selectedPlace: {},
        places: [],
        addresses: addresses
    }
  }

  componentDidMount () {
    this.plotPoints()
  }

  plotPoints () {
    let locations = this.getPoints(geocoder)
    let places = new Array()

    Promise.all(locations)
    .then((returnVals) => {
      returnVals.forEach((latLng) => {
        let place = {
          latitude: latLng[0],
          longitude: latLng[1]
        }
        places.push(place)
      })
      console.log("Places to Plot:")
      console.log(places[0].latitude)
      // places now populated
      this.setState(() => {
        return {
          lat: places[0].latitude,
          lng: places[0].longitude,
          places: places
        }
      });
      console.log("Center Lat")
      console.log(this.state.lat)
      console.log(this.state.lng)
    });
  }

  getPoints(geocoder) {
    let locationData = [];
    for (let i = 0; i < this.state.addresses.length; i++) {
      locationData.push(this.findLatLang(this.state.addresses[i], geocoder))
    }
    return locationData // array of promises
  }

  findLatLang(address, geocoder) {
    return new Promise(function(resolve, reject) {
      geocoder.geocode({
        'address': address
      }, function(results, status) {
        if (status === 'OK') {
          console.log(results);
          resolve([results[0].geometry.location.lat(), results[0].geometry.location.lng()]);
        } else {
          reject(new Error('Couldnt\'t find the location ' + address));
        }
      })
    })
  }

  displayMarkers (stores) {
    return stores.map((place, index) => {
      return <Marker key={index} id={index} position={{
       lat: place.latitude,
       lng: place.longitude
     }}
     onClick={() => console.log("You clicked me!")} />
    })
  }

  onMarkerClick (props, marker, e) {
    this.setState({
      selectedPlace: props,
      activeMarker: marker,
      showingInfoWindow: true
    });
  };

  render() {
    geocoder = new this.props.google.maps.Geocoder();
    console.log("Place Array")
    console.log(this.state.places)
    return (
      <div className="container place-map">
        <div className="row">
          <div className="col-md-12">
            <Map
              google={this.props.google}
              zoom={8}
              style={mapStyles}
              initialCenter={{
                lat: this.state.lat,
                lng: this.state.lng
              }}
              
            >
              {this.displayMarkers(this.state.places)}
              <InfoWindow
                marker={this.state.activeMarker}
                visible={this.state.showingInfoWindow}
              >
                <div>Your Location Here!</div>
              </InfoWindow>
            </Map>
          </div>
        </div>
      </div>
    );
  }
}

export default GoogleApiWrapper({
  apiKey: 'AIzaSyCOJDrZ_DXmHzbzSXv74mULU3aMu3rNrQc'
})(MapContainer);

标签: javascriptreactjs

解决方案


让组件重新渲染并显示状态需要更新的更改。现在你用初始道具更新状态。当道具改变时,您的子组件的状态不会改变,因为您只使用初始道具。因此,您可以做的是使用生命周期钩子componentWillReceiveProps ,并在其中使用新道具更新您的状态。代码可以是这样的:

componentWillReceiveProps(nextProps){
  if(this.state.lat !== nextProps.center.latitude){
    this.setState({ lat: nexrProps.center.latitude});
  }
}

您也可以对其余变量执行同样的操作。这样,每当您的道具更改时,您的状态也会发生变化,从而迫使您的组件重新渲染并反映更改。

希望能帮助到你!!


推荐阅读