简体   繁体   中英

Child component not re-rendering with changes

I have two components that are both children of the same parent component that both render a list of places - a map that loads the places as markers on the map and then a grid of place listings with a filter menu. What I want to do is communicate the filter click from the place listing component to the map component to filter the markers. To accomplish this I have a function in the parent component called handlePlaceFilter() that I am passing into the place listing child component as a prop.

I am able to trigger a console log statement from the parent component after a filter click on the child and can pass up to the parent a filtered list of places - but I can't get it to re-render either component with the filtered list of places.

Here's the parent component containing both children and the handlePlaceFilter() function:

 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> ); } } 

Here's the child component for the Place Listings:

 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> ) } } } 

And here's the child component for the 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); 

In your Child component, you are retrieving/setting places value in constructor. After which any change in props of Parent component wont notify to Child component that state value is updated, unless you add getDerivedStateFromProps(props, state) .

In this method, you will receive new props and update state from newly received props.

After updating state here (with setState, your child component's render method will execute)

For component to rerender and display the changes the state needs to update. Right now youbare update the state with initial props. When the props changes the state of your child component won't change as you are using only initial props. So what you can do is use a lifecycle hook componentWillReceiveProps and inside that update your state with new props. Code can be something like this:

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

You can do likewise for rest of the variables too. This way whenever your props changes your state will also change thus forcing your component to rerender and reflect the changes.

Hope it helps!!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM