How to force one react component to not re-render? - reactjs

I have a main react component (The entire page) that contains several other UI items in it. One of those UI items is an OpenLayers map. Another component is a text field that contains the latitude and longitude of the current mouse pointer position.
I want the text field to update whenever the mouse pointer moves on the map. In my code's current state, this works.
The problem is every time the mouse moves and I update the react state value for the latitude/longitude (which gets rendered in the text field as expected) the map re-renders. This looks terrible because the map flashes every time as it begins to load the map tiles.
So I have the mouse latitude/longitude position, but I can't get the map to not flash/stutter/strobe.
Is there a way to get a mouse event from the map component, and send it to another component on the same screen without causing the render method to redraw the map every time? Or is React the wrong tool for this project?
As a note, I'm using React Redux to handle some state values, such as the map object. All of my child components are React functional components, but the main component for the page is a React class component.
If it helps, I set the mouse listener on the map like this:
this.props.map.on('pointermove', event => {
this.setState({
mouseCoordinates: (event.coordinate),
});
});
This is my render method:
render() {
return (
<div className="main">
<p className={noMargPad}>
{this.state.mouseCoordinates}
</p>
<Map
center={fromLonLat(this.state.center)}
zoom={this.state.zoom}
>
<Layers>
<TileLayer source={osm()} zIndex={0} />
{this.state.showLayer1 && (
<VectorLayer
source={vector({
features: new GeoJSON().readFeatures(geojsonObject, {
featureProjection: get("EPSG:3857"),
}),
})}
style={FeatureStyles.MultiPolygon}
/>
)}
{this.state.showMarker && (
<VectorLayer source={vector({
features: this.state.features
})} />
)}
</Layers>
<Controls>
<FullScreenControl />
</Controls>
</Map>
</div>
);
}

One of the main advantages of using a tool like React is it will only refresh the components in the render method that have had an update to their dependent data. See this article for more information.
The fact that your map is rendering again indicates that the map, or one of its children is being passed a piece of data that keeps changing.
Based on your code, one possibility is the call to OSM(), since that's a constructor from the OpenLayers API. A new object/class instance in JavaScript could cause data re-render.

Related

Avoid unneccessary re-rendering in React dynamic nested components

The state for my project includes a list of nested animals eg:
{"europe":{"air":{name:"warbler", points:0}}}
My components are generated based on this data, and at the lowest level (the animal itself), there is a button which is currently triggering a series of callbacks to the highest level, starting a dispatch to the reducer. Every time a button is clicked, all the components from every continent re-render.
Would it be better to implement useContext, even though every level of component requires some amount of data from the state object?
I tried to implement useCallback, to prevent re-rendering but I didn't know which callbacks were causing it. What would be the best way to optimize rendering a series of nested components (without redux)?
Inside App component
{Object.entries(state.animalData).map(([continent, areas]) => (
<Continent
key={continent}
areas={areas}
totals={state.totals.continents[continent]}
handleVote={(
num: number,
animal: string,
area: string
) => triggerChange(num, animal, area, continent)}
/>
))}
Inside Continent component
<Area
key={area}
area={area}
animals={animals}
onVote={(num: number, animal: string) =>
handleVote(num, animal, area)
}
/>
Inside Area component
{animals.map(animal => (
<Animal
key={animal.name}
animal={animal}
voted={(num: number) => onVote(num, animal.name)}
/>
))}
Inside Animal component
<div>
<h4>{animal.name}</h4>
<div>
<button onClick={voted(+1)}> Upvote </button>
<button onClick={voted(-1)}> Downvote </button>
</div>
<h4>{`${animal.points} Points`}</h4>
<hr />
</div>
Your components trigger a re render because you are using inline defined functions everywhere and those aren't referentially stable. You can use the useCallback (https://reactjs.org/docs/hooks-reference.html#usecallback) hook to prevent re renders.
But you could also use a context provider (as you suggested) which holds the voted callback. That way you don't have to use prop drilling to get the function to the component where it is needed.
The basic solution for this is explained in detail here: https://medium.com/#jeromefranco/how-to-avoid-prop-drilling-in-react-7e3a9f3c8674

ReactDOM.render does not re-render the 2nd element on the list

im having problem with ReactJS, i
on index.js i have:
ReactDOM.render([<App />, <Footer />], document.getElementById('root'));
the problem is that Footer needs to add an 80px padding from the bottom only on certain pages
so on the render method i do
<div>
{isMobile && window.location.pathname.startsWith('/summary') &&
<div style={{height: 80}}></div> }
</div>
but it doesn't re-render when window.location.pathname changes,
i move around the web app and it doesn't change only when i hit F5 it renders correctly on that page.
i tried using events on window but they're aren't invoked as well...
window.addEventListener('locationchange', function(){
console.log('xxxxxxx location changed!');
})
window.addEventListener('hashchange', function(e){console.log('xxxxxx hash changed')});
window.addEventListener('popstate', function(e){console.log('xxxxxxx url changed')});
how i can make it re-render? or make Footer work as React component that can render ?
This is a common problem!
Out of the box, React is configured to operate on a single page (see Single Page Applications), which basically means it deletes what's shown on screen and renders new information when an update is required.
Naturally, simulating the routing behavior that's observable for non Single Page Apps is a little more difficult - check out React Router, which is a library created to tackle this exact issue.

Loading dynamic links based on state

I'm having trouble rendering a dynamic ReactRouter menu.
I have a component, <MyApp />. In the componentDidMount() method of <MyApp />, i'm calling an API. the API returns an array of articles. I then set the state of <MyApp /> with these new articles.
.....
this.setState({
isLoaded: true,
articles: result
});
Now, I load a new component, <ArticlePage articles={this.state.articles} />
Slight tangent - This new <ArticlePage /> is made with a function component, not a class component. The reason being, is I had trouble using ReactRouter inside Class components.
function ArticlePage(props){}... now exists. Props holds my array of articles.
Inside ArticlePage(), i want to create a reactRouter link for each of the articles in the array that i'm passing in as props. This is where i'm having trouble.
In my head, I need to loop over each of the props.articles array elements, and create a reactRouter link element..
<ul>
{props.articles.map(item =>
(
<li>
<Link to={`${url}/${item.article_id}`}>{item.title}</Link>
</li>
))}
</ul>
The problem is, my links are not being displayed on the page. Debugging suggests that when the page renders and the .map loop above runs, the array is empty. Using Chrome developer tools, I can see that the props.article for that component does in deed contain all the articles. However, i am of course looking at this object in slower time than when the page renders, so it has had time to complete.
What is it, that I am doing wrong? Perhaps I should be loading and passing the articles state in a different way?

Twinkling images in a component

I have component with navigation, on click item to child component passed in props some params, one of params - object 'itemImage' with className and url, like this:
{
url: '/static/image.svg',
className: 'absolute hidden md:block min-w-53 lg:min-w-68 mt-30 lg:mt-19 -ml-28 lg:-ml-75',
}
In child component ItemComponent:
{
itemImage &&
<img className={itemImage.className} src={itemImage.url} alt='' />
}
ItemComponent is selected from an array according to the order of the element in navigation (it is responsible for the object passed to the child component), since the list of navigation elements and elements of the array of pictures are not related and of different lengths. The sample is implemented on the basis of an index in map for objects and an index in an array with pictures, to be more precise.
Problem:
the pictures flicker as the state of the parent and the child is updated, is it possible to somehow avoid this and make the change of the picture clear without the flickering of the previous card.
You can use the below-mentioned code to render the Array of Images.
<>
{this.props.Images.map(item=>{
return (item)
})}
<p>
{JSON.stringify(this.state)}
</p>
<p>
{JSON.stringify(this.props.changed)}
</p>
<button onClick={this.props.onChange}>Change</button>
<button onClick={this.onCurrentChange}>Current State Change</button>
</>
Please check the demo here Demo
You can make somethings to try to prevent that.
1- Add a key prop to the elements. It help react understand that it is the same data from before and not re-render that piece.
2- Use react PureComponent on the flickering element https://reactjs.org/docs/react-api.html#reactpurecomponent to prevent the re-render
3 - Instead of purecomponent implement shouldComponentUpdate

What is causing my component to unmount?

I'm making an app where I have an Overlay component and a Map component rendering behind it just like this:
<div>
{showOverlay && <Overlay />}
<Map /> // this component should mount once
</div>
The overlay is shown to let the map load underneath but when I remove the Overlay (set showOverlay to false here) the Map component re-load.
At first I thought the Map component was just re-rendering, but after some digging I discovered that the component was actually re-mounting.
If I log in the componentWillMount, componentWillUnmount and render methods, the log appear in that order (which seems contradictory)
render
willUnmount
willMount
The parent does not re-mount, only the Map component do.
The Map component just render a div that reference a mapbox-gl-js map (like this https://gist.github.com/tristen/5c4b346ae38892f732504e6785d87057#file-map-js)
What can cause my component to re-mount itself like that ?
Thanks !

Resources