Reactour and focus issues on re-render - reactjs

The Scenario
I am using the Reactour library to create a guided tutorial on my website. The library allows me to interact with highlighted components, which is the desired behavior. However, my input boxes have a onBlur attribute that updates the state in a parent component, thus re-rendering the child (component where the input boxes are).
The Issue
The problem is that this re-render is messing up the focus and the user is not able to "tab" between fields (when the tutorial is open). It seems that the Reactour component is receiving the focus after the re-render, even though they have a tabIndex="-1" set by default in their component.
My Approach
I tried to set explicit tabIndex properties, but that didn't work.
I thought about having an onKeyDown listener, check if the pressed key is tab and "manually" control the focus between fields, but that seems too hacky and messy, considering I have a lot of fields in my form.
I made a CodeSandbox here to reproduce the bug. You will notice that you can tab between inputs when the Tutorial is closed, but clicking the "Start Tour" button will mess the tabIndex behavior.
Any ideas?

Just for reference, this seems to be an issue with Reactour and it was logged here. Hopefully, it will be fixed soon.
For now, as a workaround, my solution was to manually set the tabIndex of Reactour components during initialization:
setTimeout(() => {
const elements = document.querySelectorAll("#___reactour button");
elements.forEach((el) => (el.tabIndex = -1));
}, 100);
The timeout is necessary since it takes a little bit for the elements to show up in the screen.

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.

React Use-Hook-Form setFocus not Setting Focus between Bootstrap Accordions

We have a 3 section form & we are requiring the user to fill out this form in a particular order pictured below (1. From Bin, 2. Item, 3. To Bin). Upon entering the required info in a section, the next section should automatically appear with focus on the first input. These 3 sections are simply part of 1 react-hook-form.
We are noticing, however, that react-hook-form's setFocus function does not set focus on the first input when we change sections and we can not seem to determine why this is or how to make it do so. I've seen a couple setTimeout hacks, but this does not seem like the correct approach.
Here is a simple codesandbox we put together that replicates the issue.
Any help on this would be greatly appreciated!
I can only point you to the right direction - this issue is because the accordion items are hidden. If I expand them all by setting activeKey and then navigate through the inputs, it works. Accordion.Body has display=none when collapsed and you can't focus hidden elements.
I thought it would fix it, when I set focus in useEffect, after activeIndex has changed, but this didn't fix the problem:
useEffect(() => {
if (activeSection === 1) {
setFocus("itemID");
}
if (activeSection === 2) {
setFocus("binTo");
}
}, [activeSection, setFocus]);
There are also no re-renders, which could lead to loosing focus.

React-leaflet performance issue, prevent GeoJSON re-render

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!

React Spring Transition not working properly

So I've looked at the react springs docs, and I'm sure I'm just not understanding it properly. But I wanted to essentially try to make a div have an enter animation and some kind of trigger to make it leave
I have been unsuccessful in triggering this leave. I can't seem to understand what makes it trigger the leave functionality. Or maybe I'm just not understanding the transition in general which is entirely possible.
The idea is simple, I want to be able to trigger something to come into view, and then when something else happens, to trigger it to go out of view.
I took an example from the react-spring docs, and made my own fork, to demonstrate what I seem to be misunderstanding.
If you go to this codepen, and click anywhere in the display box, you'll notice some animation happening. From my understanding, the state variable isTrue should be telling the transition what state to be in. So if it's false, I would assume this would trigger leave. But it doesnt leave, as you can see from the example, it stays at the enter rule (display being block and opacity being 1).
Essentially, I'd want it to come into view initially, and when i click on it, to trigger the leave rule and have it animate out to display none and opacity 0.
Any help is appreciated.
Transition basic function is for displaying the content of an array. If you manipulate the array the transition will follow. You can add, insert and delete elements and react spring will take care the animation of each item.
When you use transition to switch on/of a single item with a boolean value. You have to modify your render method to add an extra condition ( item &&) in front of your component. As you can see in the last example here: https://www.react-spring.io/docs/hooks/use-transition
{transitions.map(({ item, props, key }) => {
const Page = pages[0]
return item && <Page key={key} style={props} />
})}
This is the modified code:
https://codesandbox.io/s/xenodochial-buck-k40bj

FocusListener on Tabs/ScaleImageLabel

I've added ScaleImageLabel to swipe Tabs. I want to calculate amount of time each tab remains in focus. I added focus listener to Tabs/ScaleImageLabel but it's not getting fired. It's working when added to the form. Any suggestions on how to achieve it?
If I understand correctly what you need is a tab selection listener. Focus listener will only work for focusable components and labels are by default non-focusable. I would recommend avoiding it since focus is used for key based navigation. It might produce different results than you would expect.

Resources