MobX React: How to only Re-render part of View - reactjs

I have a map react component and I'm dynamically adding markers.The problem is that when I add markers in the store, the whole map rerenders instead of just appending the markers to the map. Has anyone got any suggestions on how this could be fixed?? I'm pretty sure that I need to Inject the store into CPMap function exclusively, I'm just not sure how.
const CPMap = withGoogleMap((props) => (
<GoogleMap
ref={props.onMapLoad}
style={{
height: 100,
width: 100,
}}
onCenterChanged={props.boundsChanged}
defaultOptions={{ styles: this.mapStyles }}
defaultZoom={props.zoom}
defaultCenter={{ lat: props.center.lat, lng: props.center.lng }}>
<MarkerClusterer
gridSize={40}>
{
props.markers.map(({ key, position }) => (
<Marker
key={key}
position={{ lat: position.lat, lng: position.lng }}
icon={{
url: require('../../images/marker.png')
}}
/>
))
}
</MarkerClusterer>
</GoogleMap >
))
return (
<CPMap
style={{
height: 100,
width: 100,
}}
onMapLoad={(gMap) => {
map = gMap
this.props.map.fetchMarkers()
}}
boundsChanged={() => {
this.props.map.fetchMarkers()
}}
center={this.props.map.center}
zoom={this.props.map.zoom}
markers={mobx.toJS(this.props.map.markers)}
containerElement={
<div style={{ height: 'calc(100vh - 70px)' }
} />
}
mapElement={
<div style={{ height: 'calc(100vh - 70px)' }} />
} />
)
}

I'd propose you the following solution: don't pass makers directly to CMap component, but use store instead;
const markersStore = observable({
markers: []
});
Then in your component - move MarkersClusterer to the separate component and pass markersStore one level deeper:
//import markersStore or inject via mobx-react package
class MyComponent extends Component {
render() {
const CMap = withGoogleMap((props) => (
<GoogleMap
...
<Clusterer markersStore={props.markersStore} />
/>
))
return (
<CMap
...
markersStore={markersStore}
/>
)
}
}
Make your new Cluster component observable so it updates when markersStore "markers" property updates;
#observable
class Clusterer extends Component {
render(){
const { markers } = this.props.markersStore;
return (
<MarkerClusterer>
{ markers.map(...)}
</MarkerClusterer>
)
}
}
The final change is to update your fetchMarkers method so it populates the markersStore with new data.
With this solution CMap component doesn't know anything about markers and it doesn't updates when new data received. At the same time MarkersClusterer component becomes more clever and "observes" changes in the markersStore.

Related

Next js with react-leaflet window is not defined when refreshing page

im using react-leaflet in Next js but when reloading page shows "window is not defined" even i am using dynamic import with ssr:false,
i saw this question made by others here and tried answers that they offered but didn't work, also tried to make the map mounted after component but again no result,
my code:
function ContactPage() {
const MapWithNoSSR = dynamic(() => import('../Map/Map'), {
ssr: false,
})
return (
<div className={styles.map}>
<MapWithNoSSR/>
</div>
)
}
function Map(){
const [markers, setMarkers]= useState([
{cord:[12.3774729,20.446257]},
{cord:[45.3774729,10.45224757]},
{cord:[40.3774729,15.4364757]},
])
<MapContainer center={[12.374729,22.4464757]} zoom={13} scrollWheelZoom={true} style={{height: "100%", width: "100%",zIndex:'1'}}>
<TileLayer
url={`example.com`}
attribution='Map data © <a>Mapbox</a>'
/>
{markers?.map((element,idx)=>{
return <Marker
position={element?.cord}
draggable={true}
animate={true}
key={idx}
>
<Popup>
Test PopUP
</Popup>
</Marker>
})}
</MapContainer>
}}
}
as you were told in the comment, dynamic () has to go outside of the component or screen you are going to return, e. g.
import dynamic from "next/dynamic"
const MyAwesomeMap = dynamic(() => import("../components/Map/index"), { ssr:false })
export default function inicio() {
return(
<>
<p>Example Map</p>
<MyAwesomeMap />
</>
)
}
Your Map component doesn't return anything

fetch multiple image source before render react native

I would like show image on my markers, but it works only when I use the icon attribute of the marker. I need to use the subview to manage the style. When I use the ICON attribute, the view image appear (so I have two image on the map) but not showing when it's in comment.
This how I fetch the data and update the state.
componentDidMount(){
getmarkers().then(data => {
let test = []
data.forEach(element => {
test.push(
{
latlng:
{
latitude: JSON.parse(element.latitude),
longitude: JSON.parse(element.longitude)
},
name: JSON.parse(element.images)[0],
id: element.id,
image: JSON.parse(element.images)[0]
}
)
});
this.setState({ markers: test, loaded: true })
})
}
}
This is my view
{this.state.markers.map((marker, i) => (
<Marker
key={i}
coordinate={marker.latlng}
title={marker.name}
description={marker.name}
tracksViewChanges={false}
// icon={{ uri: 'https://www.testeed.com/uploads/minifiedplaces/' + marker.image}}
>
<View>
<Image source={{ uri: 'https://www.testeed.com/uploads/minifiedplaces/' + marker.image }} style={{ width: 40, height: 40, borderRadius: 6 }} />
</View>
</Marker>
))}
The issue come from Trackviewchange. If you put this value to false. the application is lagging. then if you put the value to false, the application do not load the image at first loading.
So you have to init Trackviewchange to True and create promise after the execution of your fetch/foreach, if it doesn't work, you can do a a setimeout or setinterval to change the value of trackviewchange to false to show images changes and get a better smooth user experience.
const customMarkerImage = require("../../../customMarkerImage.png");
<MapView.Marker>
<View >
<Image source={customMarkerImage} onLoad={() => this.forceUpdate()}>
</Image>
</View>
</MapView.Marker>
It is probably about this issue : https://github.com/react-native-community/react-native-maps/issues/924

react-google-maps map not updated when passed props and the marker is one location behind when updated

I have the map working when it loads, but whenever I pass props the map itself never changes.
The marker changes, but is always 1 state behind what it should be, obviously a sync issue but I'm not sure how to fix this in my component.
class Map extends React.Component {
state = {
center: { lat: 50.937531, lng: 6.960278600000038 }
}
componentWillReceiveProps = () => {
this.setState({
center: {
lat: Number(parseFloat(this.props.city.lat)),
lng: Number(parseFloat(this.props.city.lng))
}
});
}
render() {
console.log(this.state.center)
return (
<div>
<MapDetails
googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyB-L-IikkM6BZ_3Z2QIzbkx4pdAkP76tok&v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100vh`}} />}
mapElement={<div style={{ height: `100%` }} />}
mapCenter={this.state.center}
/>
</div>
)
}
}
const mapStateToProps = (state) => {
return { city: state.CityNameReducer }
}
export default connect(mapStateToProps)(Map);
const MapDetails = withScriptjs(withGoogleMap(props =>
<GoogleMap
defaultZoom={12}
defaultCenter={props.mapCenter}
>
<Marker
position={props.mapCenter}
/>
</GoogleMap>
));
The biggest question I have is why the map itself is not updating?
Add a key to GoogleMap component. a Key should be unique each time provided. for your testing use new Date().getTime() function.
<GoogleMap key={new Date().getTime()}/>
as I mentioned its for only testing so make sure to provide this key in better way
I may recommend you to use shouldComponentUpdate(nextProps, nextState) {} instead of componentWillReceiveProps().
I recommend you to read the following page, to understand how it works.
Updating and componentWillReceiveProps
I don't have more information about what is happening in the parent of your component, so I cannot go deeper to the problem. But I think you can get the solution by changing that.

Passing props in child component from state of parent component

I am unable to use the state from Parent Component to child component on Google Maps component. This may sound a very basic question, however I am trying to understand the difference between one of props working and other not.
So I have a google maps component (import GoogleMapReact from 'google-map-react')
static defaultProps = {
center: { lat: 62.10281, lng: -84.14565 },
zoom: 11
};
constructor(props) {
console.log(props);
super(props);
}
render() {
return (
<div className='google-map' style={{ height: '100%', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ key: 'somekey' }}
center={this.props.center}
defaultZoom={ this.props.zoom }>
<AnyReactComponent
lat={ this.props.lat }
lng={ this.props.lng }
text={ 'Wheres Waldo?' }
/>
</GoogleMapReact>
</div>
)
}
and then from parent component
<MapComponent lat={this.state.latitude} lng={this.state.longitude}
center={{lat: this.state.latitude, lng: this.state.longitude}}
I have checked the value to lat, lng and center in constructor and I am getting them as expected still map doesn't work
However, If I hardcode the value in center leaving the lat and lng getting value from state, then it does work.
Now this works, Can someone please explain this concept and the difference and how to overcome it? Your help is appreciated.
<MapComponent lat={this.state.latitude} lng={this.state.longitude}
center={{lat: 62.10281, lng: -84.14565}}

Nested Match routes with React Router v4 and MatchWithFade

This question is a follow-up to this:
Trouble with React Router v4 and MatchWithFade
I've got another (potentially silly) question about using MatchWithFade and React Router v4. What I'd like to do is have nested routes, so a top-level component might have this:
<MatchWithFade pattern='/one' component={One} />
...and then One might have this:
<Match pattern='/one/one' Component={OneOne} />
This doesn't strike me as an unusual pattern (though maybe it is). In any event, the behavior I'm observing is that, using the above example, if I load OneOne, it gets mounted, and then componentWillUnmount is immediately called. Had I to guess, I'd say that TransitionMotion is keeping track of a (perhaps hidden) instance of OneOne, and once the transition is complete, it unmounts that hidden component. As far as the basic UI is concerned, OneOne is rendered. However, if componentWillUnmount does any cleanup (like, say, removing something from Redux), then of course that action is fired, and any data tied to OneOne is blown away.
Here's a complete example that illustrates the problem:
import React, { Component } from 'react';
import BrowserRouter from 'react-router/BrowserRouter'
import { TransitionMotion, spring } from 'react-motion'
import Match from 'react-router/Match'
import Link from 'react-router/Link';
const styles = {
fill: { position: 'absolute', top: 0, left: 0 }
};
const MatchWithFade = ({ component:Component, ...rest }) => {
const willLeave = () => ({ zIndex: 1, opacity: spring(0) })
return (
<Match {...rest} children={({ matched, ...props }) => {
return (
<TransitionMotion
willLeave={willLeave}
styles={matched ? [ {
key: props.location.pathname,
style: { opacity: 1 },
data: props
} ] : []}
>
{interpolatedStyles => {
return (
<div>
{interpolatedStyles.map(config => (
<div
key={config.key}
style={{...styles.fill, ...config.style }}>
<Component {...config.data}/>
</div>
))}
</div>
)
}}
</TransitionMotion>
)
}}/>
)
}
const TwoOne = () => {
return (
<div>Two One</div>
)
}
class TwoTwo extends Component {
componentWillUnmount() {
console.log("TwoTwo will unmount")
}
render () {
return (
<div>Two Two</div>
)
}
}
const TwoHome = () => {
return (
<div>Two Home</div>
)
}
class One extends Component {
componentWillUnmount () {
console.log("ONE UNMOUNTING")
}
render () {
return (
<div style={{ width: 300, border: '1px solid black', backgroundColor: 'orange', minHeight: 200}}>
One one one one one one one one one one<br />
One one one one one one one one one one<br />
</div>
)
}
}
const Two = () => {
return (
<div style={{ width: 300, border: '1px solid black', backgroundColor: 'yellow', minHeight: 200}}>
<Match pattern='/two/one' component={TwoOne} />
<Match pattern='/two/two' component={TwoTwo} />
<Match pattern='/two(/)?' exactly={true} component={TwoHome} />
</div>
)
}
class App extends Component {
render () {
return (
<BrowserRouter>
<div style={{padding: 12}}>
<div style={{marginBottom: 12}}>
<Link to='/one'>One</Link> || <Link to='/two'>Two</Link>
|| <Link to='/two/one'>Two One</Link>
|| <Link to='/two/two'>Two Two</Link>
</div>
<div style={{position: 'relative'}}>
<MatchWithFade pattern='/one' component={One} />
<MatchWithFade pattern='/two' component={Two} />
</div>
</div>
</BrowserRouter>
)
}
}
export default App;
If you load this and open a console, toggle between the One and Two links. You'll see the cross fade happen in the UI, and you'll see "ONE UNMOUNTING" in the console when the transition from One to Two completes. So that's right.
Now, click between Two One and Two Two. In this case, when Two One is clicked, you'll immediately see "TwoTwo will unmount" in the console, which is good. However, if you click Two Two, you'll see "TwoTwo will unmount" after about a second--which I take to be the amount of time the parent MatchWithFade takes to execute.
So I'm not sure what's going on here. Is my code just busted? Am I doing something that RRv4 cannot support? Have I uncovered a bug?
Any help/guidance is appreciated!
Your issue is your use of props.location.pathname as a key. This should always be the same for a component, but the way that you have written it, it changes each time that you navigate. Try changing this:
const styles = {
fill: { position: 'absolute', top: 0, left: 0 }
};
to:
const styles = {
fill: { position: 'relative', top: 0, left: 0 }
};
and you will see that you are rendering two instances of <Two> (one for each key).
If you were to use a constant key, such as rest.pattern (the pattern associated with this <Match>), your issue would go away.
styles={matched ? [ {
key: rest.pattern,
style: { opacity: 1 },
data: props
} ] : []}

Resources