【问题标题】:Child component not re-rendering with changes子组件未随更改重新渲染
【发布时间】:2019-07-28 19:00:28
【问题描述】:

我有两个组件,它们都是同一个父组件的子组件,它们都呈现一个地点列表 - 一个将地点加载为地图上标记的地图,然后是一个带有过滤器菜单的地点列表网格。我想要做的是将过滤器点击从地点列表组件传递到地图组件以过滤标记。为了实现这一点,我在父组件中有一个名为 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>
            )
        }

    }

}

这是地图的子组件:

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

【问题讨论】:

  • 介意放任何子组件的代码吗?
  • 刚刚更新并添加到子组件代码中。我已经一起破解了一些东西来过滤子组件中的地点列表,我的大问题是我无法将过滤后的地点列表传达给地图以过滤标记。
  • 请从代码中删除 API 密钥

标签: javascript reactjs


【解决方案1】:

在您的子组件中,您在构造函数中检索/设置位置值。之后,Parent 组件的 props 的任何更改都不会通知Child 组件状态值已更新,除非您添加getDerivedStateFromProps(props, state)

在此方法中,您将收到新的道具并从新收到的道具中更新状态。

这里更新状态后(使用setState,你的子组件的render方法会执行)

【讨论】:

  • 我在 getDerivedStateFromProps 上进行了一些谷歌搜索,并尝试将以下内容添加到 Map 子组件中,但它没有改变任何内容: static getDerivedStateFromProps(props, state) { if (props.places !== state.地方){返回{地方:props.places,}; } // 如果状态没有改变则返回 null return null; }
  • 方法本身是否正在运行?你使用的是哪个版本的 react?
  • 从概念上讲,您知道为什么孩子没有获得新的价值观?当父母更新其道具时,孩子的状态不会更新。
  • 从概念上讲我明白了,但我在实际实施中苦苦挣扎。我正在运行 react 16,我将其更改为设置状态,但它仍然无法正常工作。如果我添加一条日志语句,看起来甚至不会触发 getDerivedState: static getDerivedStateFromProps(props, state) { if (props.places !== state.places) { console.log("getDerivedState Triggered!") this .setState(() => { return { places: props.places } }); } // 如果状态没有改变则返回 null return null; }
  • 你能拉出一个带有托管版本的页面吗?小提琴类型的位?
【解决方案2】:

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

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

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

希望对你有帮助!

【讨论】:

  • 如果我在下面添加代码,它会重新渲染地图,但现在没有任何标记:componentWillReceiveProps(nextProps){ if(this.state.places !== nextProps.places){ this. setState(() => { return { places: nextProps.places } }); } }
  • 您在地点上实现的地图功能不应在构造函数内部。理想情况下,您的构造函数应该只有状态和函数绑定。您可以将该代码移至componentDidMount,看看它是否有效。
  • 另外,您不需要将函数作为参数传递给 setState,您可以直接传递对象而不是从函数中返回它。它会让你的代码更加紧凑。
猜你喜欢
  • 2020-06-26
  • 1970-01-01
  • 1970-01-01
  • 2020-07-18
  • 1970-01-01
  • 1970-01-01
  • 2018-08-12
  • 2019-06-01
  • 2020-02-13
相关资源
最近更新 更多