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.
Related
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!
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).
I have a TabView component that has multiple tabs containing components. These components have entire hierarchies of other components. How could I know from any child component nested arbitrarily deep in one of these hierarchies whether or not it's parent tab is in focus in the TabView?
Preferably, I would want to implement this similar to react-navigation's withNavigationFocus (or an equivalent hook) so that any component can know if it's in tab focus without having to pass props down the chain of components. I'm thinking you could have a TabViewContext that components can register themselves as focus listeners to by doing a useContext. The TabViewContext would be provided by the TabView and the TabView would be responsible for determining what registered listeners are in focus when tabs change. My dilemma is I don't know how the TabView could determine efficiently what nested child components come into focus when the tab changes. Any ideas?
In case the other parent tabs are hidden, you could test for visibility in plain JS, rather than have a much more complex solution...
Checkout this answer on how to do this.
So components that care about the visibility of their parent tab could use a ref for their own DOM elements and test whether they're visible or not. You could build this into a simple helper function or a hook
EDIT:
I'd suggest going with something like this:
Each Tab will provide a context with method for any descendant to register a callback that will be called when the Tab is hidden. The TabView can pass a "isVisible" prop to each tab (if it doesn't already), so Tab can know when its display changes.
When a Tab changes from visible to hidden. All registered callbacks will be called.
I would of course write a hook or a helper function to create this TabVisibilty context so each Tab component can use it in a reusable manner.
https://codesandbox.io/s/vjonpw8xzl
(not responsive, view in new window/fullpage to see correctly)
desired result: when clicking the down(expand)icon on each card, they operate independently of each other, opening/closing the expanded menu className: expanded by toggling on the className: 'show' and className: 'hide'
whats happening: the state in app is getting updated, when the expand icon is clicked, and all of the expanded menus are opening in unison.
i understand why this is happening, i just don't know how to fix it.
if it were my own data, i'd add this to each object in state
isExpanded:false
and toggle it, but since a load of data is coming in from an API i don't know how to do this. any help is appreciated.
Instead of handling your state logic for isExpanded in the App component you can move it to the Character component and remove all isExpanded / toggleExpanded props.
You need to decide that which states are actually defining the component and are needed for it. As isExpanded & toggleExpanded are something specific to character component they should reside in that component.
According to the docs, one should avoid having multiple components with state. I am in the situation where I want to make a text box that automatically expands vertically as the user writes, and for that I'm using this trick http://www.impressivewebs.com/textarea-auto-resize/, which means I need to get the height of a component. Now, I've been playing around with it a bit, and it doesn't seem feasible to pass a ref to my parent component which contains state, so the easy way out would be to keep a piece of state in the component with the textbox, and then use the ref from there.
This got me thinking, how exactly do multiple state components negatively affect my app? Is it only maintainability / comprehensability? Or are there actual performance issues with it? I've noticed a lot of open source react components that you would just plug in to your app keep state, meaning if you use open source components, chances are you will have several state components in your app.
It's totally ok to use local state for this kind of tricks on DOM. It's even better approach, than to share such implementation details to parent components.
In general, use this places for state:
Application-wide data in stores outside React (redux, flux-store, observables)
Form temporary data can be placed in store also. But if don't need it anywhere else except form, it's better to place this data in form component.
Tricks on DOM, short living and very local state can be placed in component that just need it
are there actual performance issues with it?
No. If you'll place all your state in components, your application will become even faster. Because when you update local state, only this component and it's childs updates.
But you shouldn't do that, because it kills maintainability.
lot of open source react components that you would just plug in to your app keep state
If component doesn't allow you to control it through the props - it's bad component. Usually open source components written to be easier to use, so they provide nice defaults, that allow you to just place component to your application, and be happy with that.
For example, Tabs component usually controlls selected tab using local state. But also it takes selectedTab and callback onSelect, so you can control it by yourself.
Stupid components (like your textarea component) should not have state with data. But they can have their own UI state.
In this case you can easily keep textarea height in state of stupid component.