Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I dynamically update collection of MapMarker images based on whether selected or not?

I've been baffled by this recently, and I'm looking to dynamically update map marker images based on whether a map marker is selected/active or not (Each MapMarker has a category of which references either the active image or the default image from two collections of images; interestIcons and interestIconsSelected. Essentially I have a collection of MapMarkers that are rendered out with mapping through the map markers collection. Each MapMarker is a child component of MapView.Marker.

I would like to render out all MapMarkers in a default non-selected / non-active state having the default image from interestIcons, when selected, that MapMarker image should change to the active image from interestIconsSelected, when another MapMarker is selected the previous selected should revert back to the default image, and the new should change to the selected image.

Currently, I'm able to render out the map markers with the default image but the when selecting the mapmarker the image does not seem to change immediately unless you were to zoom out / zoom in again i.e. to cause some kind of re-render, I would like it so clicking on a MapMarker would immediately cause the image to update please.

MapScreen.js: Renders out MapView for all of the MapView.Marker MapMarkers through a map.

        <MapView
            ref={map => { this._map = map }}
            style={Styles.Map.map}
            initialRegion={this.props.region}
            onRegionChange={this.handleRegionChange}
        >
            {
                this.props.events
                    .filter(this.eventFilterTimeNearFuture)
                    .filter(this.eventFilterTimeNotPast)
                    .filter(this.eventFilterDistance)
                    .filter(this.eventFilterInterest)
                    .map(e =>
                        <MapView.Marker
                            key={e.id}
                            onPress={() => this.handleLocationPush(e)} // Set selected event state
                            coordinate={e.location}
                        >
                            <MapMarker
                                event={e}
                                size={this.state.markerSize}
                                selected={this.state.selectedEvent} // The selected event set by state call.
                            />
                        </MapView.Marker>
                    )
            }
            { !this.props.regionMock &&
                <MapView.Marker
                    key={'userLocation'}
                    coordinate={this.props.region}
                >
                    <MapMarker size={'user'} />
                </MapView.Marker>
            }
        </MapView>

MapMarker.js

import {interestIcons, interestColors, interestIconsSelected} from "../../utils/Icons";

import {Styles} from '../../StyleProvider';

class MapMarker extends React.Component {

constructor() {
    super();
    this.state = {
        initialized: false,
        active: false,
    };
};    

componentWillReceiveProps(nextProps) {
    if (!this.state.initialized) {
        console.log('initialization');            
        this.setState({initialized: true});
    }
    else {
        // If the nextProps.selected prop exists which it will
        if (nextProps.selected) {
            // If the nextProps.selected props id equals the this event id then selected else non-selected.
            if (nextProps.selected.id === nextProps.event.id) {
                console.log('SELECTED: ' + JSON.stringify(nextProps.selected));
                // set staae to active
                this.setState({
                    active: true
                });
                console.log(interestIconsSelected[nextProps.event.interest[0]]);
            } else {
                // set state to not active
                // console.log('NON-SELECTED: ' + JSON.stringify(nextProps.event));   
                this.setState({
                    active: false
                });                
            }
            this.forceUpdate();
        }
    }
}

 markerIcon(interest) {
    return this.state.active ? interestIconsSelected[interest] : interestIcons[interest];
 }

renderIcon() {
    if (this.props.event.type === 'Event') {
        return (
            <Image
                source={this.markerIcon(this.props.event.interest[0])}
                style={Styles.MapMarker.eventImage}
            />
        )
    }
}

The componentWillReceiveProps(nextProps) is still a work in progress and that indicates 'well enough' the current selected event and al the non-selected events.

I've attempted to set image source to use say this.state.image and then setting the image state in componentWillReceiveProps respectively i.e.

if (nextProps.selected) {
    // If the nextProps.selected props id equals the this event id then selected else non-selected.
    if (nextProps.selected.id === nextProps.event.id) {
        console.log('SELECTED: ' + JSON.stringify(nextProps.selected));
        // set staae to active
        this.setState({
            active: true,
            image: this.markerIcon(nextProps.event.interest[0], true)
        });
        console.log(interestIconsSelected[nextProps.event.interest[0]]);
    } else {
        // set state to not active
        console.log('NON-SELECTED: ' + JSON.stringify(nextProps.event));   
        this.setState({
            active: false,
            image: this.markerIcon(nextProps.event.interest[0], false)
        });                
    }
}

renderIcon() {
    if (this.props.event.type === 'Event') {
        return (
            <Image
                source={this.state.image}
                style={Styles.MapMarker.eventImage}
            />
        )
    }
}

Change image state does seem to work more effectively in that the image would change immediately, but it then seems that the image on initial render wouldn't be set at all, so it would be just an empty icon until selected.

Thanks very much, appreciate any help at all.

Update: Attempted defining Image component under MapView.Marker and this does not work.

this.state.markers
.map(e =>
    <MapView.Marker
        key={e.id}
        onPress={() => this.handleLocationPush(e)}
        coordinate={e.location}
    >
        {/* <MapMarker
            event={e}
            size={this.state.markerSize}
        /> */}
        <Image
            source={this.state.selectedEvent === e ? interestIconsSelected[e.interest[0]] : interestIcons[e.interest[0]]}
            style={Styles.MapMarker.eventImage}
        />
    </MapView.Marker>
)

BUT this works although you don't appear to be able to apply styling to MapView.Marker but this isn't an implementation I would like as I would like to keep the custom MapMarker component

this.state.markers
.map(e =>
    <MapView.Marker
        key={e.id}
        onPress={() => this.handleLocationPush(e)}
        coordinate={e.location}
        image={this.state.selectedEvent === e ? interestIconsSelected[e.interest[0]] : interestIcons[e.interest[0]]}
    />
)

The above two snipets of code with either using the image prop directly on MapView.Marker or having an Image component directly under MapView.Marker are no good as using a MapMaper Child Component.

like image 446
Michael Avatar asked Sep 19 '17 08:09

Michael


1 Answers

you use componentWillReceiveProps life cycle method it did not run at first render also you use this.state.initialized with is false in constructor state so this will make you need twice clicks to make it active

componentWillReceiveProps(nextProps) { // it did not run at first render
  if (!this.state.initialized) { // this.state.initialized with is false in constructor
    console.log('initialization');            
    this.setState({initialized: true});
  }
  .......
}

you can remove completely your componentWillReceiveProps if you do something like this

markerIcon() { //did not get interest directly instead access it from props
  return this.props.selected.id === this.props.event.id ?
   interestIconsSelected[this.props.event.interest[0]] 
   : 
   interestIcons[this.props.event.interest[0]];
}

here you compare two objects with identical comparison as the are deep you can use some thing like this instead to not get babel confused see more

<Image
  // use this.state.selectedEvent.id  === e.id instead of this.state.selectedEvent  === e
  source={this.state.selectedEvent.id  === e.id  ? interestIconsSelected[e.interest[0]] : interestIcons[e.interest[0]]}
  style={Styles.MapMarker.eventImage}
 />

I hope that helps Thanks

like image 71
Mohamed Khalil Avatar answered Nov 09 '22 00:11

Mohamed Khalil