Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Fix #5084: Fixfox Tab switch issue #5179

Merged

Conversation

balajis-qb
Copy link

Close #5084

Description

As @avazhenin mentioned in the attached ticket, we're getting an issue while we try to switch to the next input control via Tab Key on Firefox browser.

To Reproduce

Even though @avazhenin shared his code sample along with a video explaining the issue, I'll share a simple way to recreate the issue. Goto https://reactdatepicker.com/ via FireFox and click on any date input, now try to press tab when the calendar is getting rendered. We expect it to auto-close the calendar and switch to the next code sample. But instead of that it'll refocus the same date input again. To make it work, we have to press ESC first (which will close the opened calendar popup) and then press Tab, it'll work. In this case alone it'll work, because as the datepicker is already closed, it won't disturb the tab key press.

Try the same thing in Chrome Browser, it'll auto-close the opened calendar popup and switch to the next code sample editor.

Reason behind this issue

image

The reason why this issue occurs whenever we press Tab key over the input, we call setOpen(false) to close the open popup so that the browser default focus switch will happen to the next control.

But as I highlighted in the above screenshot, while performing the state update we manually call blur operation on the date input. We do a state update and along with setting blur to the DOM element. This issue arises from a combination of

1. JavaScript Event Loop & React's Rendering Lifecycle:

  • In the code we call setBlur in the callback of setState assuming the blur will happen after the update of state. But React's state updates are asynchronous and may not complete immediately after setState(). The callback of setState runs after the state update, but not guarantee that the DOM has been fully updated and rendered (unlike the useEffect call).

2. Browser-Specific Focus Behaviour:

  • FireFox (& Safari) enforce focus/blur rules more strictly compared to Chrome's V8 engine. As a result during re-render, the browser might reapply focus back to the input, overriding the previous instant blur() call made by us in-between the state update.

Changes/Solution

Wrapping the blur() call inside a setTimeout defers it to the next macrotask, ensuring that React's render and browser focus management complete before the blur operation is executed.

As we wrapped setBlut call inside the setTimeout, we need to move the focus logic also to setTimeout, as in the case of Esc key press we kind of blur and send focus back to the input. So in this PR I moved the focus and blur call inside setTimeout.

I created 2 new methods and moved our existing blur and focus call to it instead of updating the existing methods names. Updating the existing method names make me update many lines as these functions are used in many places. So to keep my fix simple and at the same time to emphasize the change I made, I created these 2 new helpers.
image

I updated the existing test cases to adapt with my fix and also added a new test case to check the Tab order.

Contribution checklist

  • I have followed the contributing guidelines.
  • I have added sufficient test coverage for my changes.
  • I have formatted my code with Prettier and checked for linting issues with ESLint for code readability.

Balaji Sridharan added 2 commits October 20, 2024 10:44
…n to the next event loop

- This issue is caused due to the defer blur made in the commit 3682222 is getting executed after the focus operation for the ESC key press

Close Hacker0x01#5084
Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ This pull request was sent to the PullRequest network for review. Expert reviewers are now being matched to your request based on the code's requirements. Stay tuned!

What to expect from this code review:
  • Comments posted to any areas of potential concern or improvement.
  • Detailed feedback or actions needed to resolve issues that are found.
  • Turnaround times vary, but we aim to be swift.

@balajis-qb you can click here to see the review status or cancel the code review job.

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PullRequest Breakdown

Reviewable lines of change

+ 39
- 11

58% TSX (tests)
42% TSX

Type of change

Fix - These changes are likely to be fixing a bug or issue.

Copy link

codecov bot commented Oct 21, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.91%. Comparing base (5cbaa12) to head (2019c8e).
Report is 6 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5179   +/-   ##
=======================================
  Coverage   96.91%   96.91%           
=======================================
  Files          29       29           
  Lines        3366     3370    +4     
  Branches     1396     1410   +14     
=======================================
+ Hits         3262     3266    +4     
+ Misses        104      102    -2     
- Partials        0        2    +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no blocking concerns; the change looks correct. All I see is a minor nit where code can be simplified using modern features.

Image of Jacob Jacob


Reviewed with ❤️ by PullRequest

src/index.tsx Outdated
safeFocus = () => {
setTimeout(() => {
if (this.input && this.input.focus) {
this.input.focus({ preventScroll: true });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call can be simplified into this.input?.focus?.({ preventScroll: true })

🔹 Simplify Code (Nice to have)

Image of Jacob Jacob

src/index.tsx Outdated
safeBlur = () => {
setTimeout(() => {
if (this.input && this.input.blur) {
this.input.blur();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise into this.input?.blur?.()

🔹 Simplify Code (Nice to have)

Image of Jacob Jacob

Copy link
Author

@balajis-qb balajis-qb Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just moved the existing code into these new methods for the fix. I have updated the code as per your suggestion.

@martijnrusschen martijnrusschen merged commit cdc3dfa into Hacker0x01:main Oct 21, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Firefox tabIndex problem when using open prop
2 participants