简体   繁体   English

子组件不会随着更改而重新呈现

[英]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. 为此,我在父组件中有一个名为handlePlaceFilter()的函数,我将该函数作为道具传递到地方列表子组件中。

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: 这是既包含子项又包含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> ); } } 

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: 这是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) . 之后,除非添加getDerivedStateFromProps(props,state) ,否则Parent组件的prop的任何更改都不会通知Child组件状态值已更新。

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) 在这里更新状态后(使用setState,将执行子组件的render方法)

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. 因此,您可以做的是使用生命周期挂钩componentWillReceiveProps并在其中使用新的道具更新状态。 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!! 希望能帮助到你!!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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