React-leaflet performance issue, prevent GeoJSON re-render - reactjs

I have the following issue:
I would like to create a simple choropleth map using react-leaflet's GeoJSON component. I also want to have a tooltip that shows a value on hover over a GeoJSON feature. The problem is performance. A CodePen example can be found here: https://codepen.io/timester-the-typescripter/pen/gOXGKOY
I attach event handlers to each GeoJSON feature and set a state variable in my "main" component that holds the value for the currently hovered area.
const [selected, setSelected] = React.useState(null);
My tooltip relies on that state variable to show its value. Because every mouse hover event causes main component state change, the GeoJSON component gets re-rendered all the time. This is not a huge problem in the CodePen example, but for a full world map it is unfortunately.
I assume that the state change causes this because if I comment out the 2 setSelected lines (line 55, and line 67 in CodePen), the re-renders (calls to createGeoJSON ) stop, and the event handlers run a lot faster as the profiler pictures show below.
Mouseout event with state change:
Mouseout event with state change commented out:
I tried many solutions with no success. For example I think I can't memoize the GeoJSON component because the Tooltip must be its child and that depends on the state of the main component.
In the future I want to add more components depending on the hovered state variable like a legend, etc.. and maybe that variable will be a bit more complex than a simple number too.
What options do I have here? I had another stackowerflow question about this, but then I did not understand the problem completely, so it was not super focused. I'm at the point where I'm thinking about rewriting it in Angular. I found react and react-leaflet very pleasant to work with, until this issue came up.

The problem is that currently you are recreating the map with the appropriate Tooltip on each mouseover via the state. Instead, you should bind all Tooltips at map creation with layer.bindTooltip(). This will allow you to just show/hide them without having to recreate the map again, since they are already created and their creation will not rely on state.
See this github issue for an example with Popups (but the logic for Tooltips should be the same): https://github.com/PaulLeCam/react-leaflet/issues/33

I got help from this reddit comment https://www.reddit.com/r/reactjs/comments/std46f/comment/hx3yq34/?utm_source=share&utm_medium=web2x&context=3
The solution I applied based on this is not to use the Tooltip as a child component, but bind a tooltip to each layer in the onEachFeature method.
const onEachFeature = useCallback((feature, layer) => {
layer.bindTooltip(feature.properties.COUNT, {sticky: true});
...
This way I could also wrap the GeoJSON component in a useMemo() as there were no longer dependencies on the selected state. I also wrapped the style and onEachFeature functions in useCallback().
This combination solved the issue!

Related

React & Canvas: changing state is breaking Canvas functions

I am trying to use a parent component to control animations in a child Canvas element. Specifically I want an animation to happen when a user inputs a correct answer.
It works until the user changes the state in the parent component, and then it no longer works.
I've made a stripped-back, minimal version of my code here to show my issue: https://codesandbox.io/s/epic-leaf-08jqvy?file=/src/App.js
My desired behaviour is that the red box bounces when a user clicks submit. That happens if they don't type anything in the input box, but as soon as you enter anything into there - changing state and re-rendering the component - the button no longer triggers the animation in the Canvas child component.
As far as I can tell, the issue is to do with changing the state when inputing text. If I make a version where the input is just assigned to a variable, it works fine, but I need to be able to use state and re-render other parts of it.
I have put a console.log in the jump() function, so I can see that it is being called, but no animation is taking place in the canvas.
I assume that what's happening is that everything is being re-rendered when the state changes, and so the useRef is no longer tracking to the right thing.
Things I've tried:
putting the canvas in a memoized component to prevent it from re-rendering
using eventlisteners to see if I can trigger the animations in other ways - keydown ones work, but I need the user to be able to type, so I tried other ones (like hashchange or audio.play) but neither of those worked.
You can see the thing I'm actually trying to build here: https://papaya-platypus-86565f.netlify.app/play Basically users answer questions and an animation plays depending on whether they're right or wrong, to give it a game-y feel.
Thanks!
I like your red box as well as your reasoning. Yes, the input state changing on keystroke is causing the entire App component to re-render. Note that your App.js component has a lot going on (all good stuff), such as your Box class instantiation, your canvas instantiation, etc.
The key is to break your components into smaller parts, particularly separating stateful components from non-stateful components. We don't want your canvas re-mounting on every input change, so we make them sibling components!
Here's a working example of your code, just in smaller components:
https://codesandbox.io/s/strange-julien-d3n4zm
I hope this helps.

How to select items in app and updating the state in react

I want to build a simple app like in picture attached with react js, I just cannot find the right idea of:
How to "select" photos(or item) in an application and have the "cart"-like component appear at the bottom when at least one photo/item is selected(and close and deselect all already selected photo/items) and expand the cart-like component at the bottom when clicked to show what's been already selected.
What is the best approach to this?
P.S I'm new to react with a big idea in mind xD
app's view
This question definitely needs more information, but I will try to point you in the right direction. There are hundred of ways to create the UI/functionality you are describing but here is a very generic overview;
The "items" (Img1-6) looks like a grid of ShopItem components, possibly in a CSS Grid / flexbox. Each ShopItem would probably make use of an onClick method to "do something" when it is clicked - this may involve updating a state (using the useState react hook) somewhere which tells you if a ShopItem is checked or not. It could also potentially use a callback method to do something in the parent when the items are checked.
I imagine that each ShopItem may own its own "checked" state or may update a global state (Such as Zustand or Redux) or a Context (React) when it is toggled on and off. The checked state of a ShopItem would also inform the UI of the component.
The "cart-like" component could be looking at the global state/context/callback from the item component, and could change based on its value and how many checked items there are. (Eg/ checkedItems !== 0 ? show : don't show)
This is just one way in which this UI can be built, if you would like a more specific solution, please edit your question to include code snippets and what you've already tried.

Sharing Draft.js toolbar across multiple editors?

I'm struggling to figure out how to make a single toolbar work across multiple editors - Only the current editor in focus should be effected by the toolbar. Is this possible? Is there a good example out there for Draft.js?
What you can do is:
It is good to have global state in use for this case
(for React hooks)
Pass your toolbar the state and setState for your editor.
When you focus on editor dispatch the state and setState for that editor to global state and make Toolbar subscribe to it.
also for the editor whenever state changes, you need to update the global state as well.
by this you will achieve managing editors from one toolbar.
I recommend you to not use Redux Toolkit as your global state library. Because when you will try to save the state it will throw an error (EditorState is nonserializable).

Mapbox click listener on base map fails when layers are added

I have a map within my react project where I want to use a click listener on the base map to find the coordinates of the point clicked. It has worked fine for a while now.
The problem is that I've added a few layers using clusters, some of which have click listeners, and now clicking anywhere on the map gives me the error "There is already a source with this ID." I think I get why - there's a click, but not on any of the layers.
I can't find any information on how to make the base map a layer, and thereby get an id for it to pass to the click listener. So my question is, how to I attach a click listener to the base map while still using layers that may also have click listeners, too.
So that was a big pain! What was happening was that I was passing a function down to the component that contained the click listener and that function changed the state of the parent component. Given that this is react, it caused the child components to rerender and thus Mapbox to error that there was already a source with the same id because the source had never been removed.
The solution was to wrap the code that added the source and attendant layers as a component, then create a cleanup component that contained removeSource (see here) and run that on unmounting. I'm using the new hooks api, so I'm running a setLayers function in the useEffect hook, then returning cleanup from useEffect, but I bet this sort of idea would work fine with componentDidMount and componentWillUnmount.

Sharing state between all instances of same component?

I have a Tooltip component that when hovered displays a simple tooltip.
When you mouseLeave the component, a setTimeout fires, and when it ends, the tooltip is closed (setState({ open: false })).
Now I'd like to add a behavior to reflect the one of the native OS tooltips:
When you mouseLeave a tooltip, but instantly mouseEnter a different tooltip, the previous tooltip is instantly closed, and the new one gets opened.
To do so, I need to have a shared state between all the instances of Tooltip component. I could use Redux but it seems a bit overkill for a so simple task (I'd need a container that interacts with the store and makes an action and a property available).
Are there simpler solutions?
The best way to share information between ReactComponent is the Flux architecture. Redux is one of them.
A more simple option is to use the browser native storage used to store temporary information : it is similar to global variable but with particular scope and duration definitions.
Move the shared state into the state of the parent component of all Tooltips, have this parent define a method setWhatever to set the value, and pass this method to Tooltip components via a property. This way, children can call their setWhatever property, which is really the one of their parent, when they need to change the state.

Resources