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

Bug: autoFocus broken inside <dialog /> #23301

Open
jantimon opened this issue Feb 15, 2022 · 14 comments
Open

Bug: autoFocus broken inside <dialog /> #23301

jantimon opened this issue Feb 15, 2022 · 14 comments

Comments

@jantimon
Copy link

jantimon commented Feb 15, 2022

React version: 17 and 18.0.0-rc.0-next-27b569969-20220211

Steps To Reproduce

  1. render <input /><input autoFocus /> inside <dialog />
  2. execute the showModal() method of the dialog
  3. you will notice that react will not set focus to the correct input element

DialogAutoFocus

Link to code example:

https://codesandbox.io/s/dreamy-meninsky-460wbr?file=/src/App.tsx

The current behavior

In Chrome and Safari TP the element with autofocus="true" will receive focus.
However the element with autoFocus={true} will not receive focus.

The expected behavior

From the html-spec https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute

The autofocus content attribute allows the author to indicate that an element is to be focused [...] as soon as the dialog within which it finds itself is shown

From https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus

The autofocus global attribute is a Boolean attribute indicating that an element should be focused on page load, or when the <dialog> that it is part of is displayed

Therefore autoFocus={true} should also set the focus similar to autofocus="true" for elements inside <dialog />

@jantimon jantimon added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Feb 15, 2022
@sammy-SC sammy-SC added Type: Bug and removed Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug labels Feb 16, 2022
@mmarkelov
Copy link
Contributor

@jantimon until it's not fixed you can try this workaround:

export default function App() {
  const [showDialog1, setShowDialog1] = useState(false);
  const [showDialog2, setShowDialog2] = useState(false);
  return (
    <>
      // ...
      <Dialog open={showDialog2} onClose={() => setShowDialog2(false)}>
        <input value="don't focus on me" />
        <input value="please focus me" autoFocus={showDialog2} />
      </Dialog>
    </>
  );
}

@jeongwoopark0514
Copy link

Hello, @Jahb, @Mirijam1, @budihan, and I are a team from TU Delft. We are taking a course called Software Architecture. We chose ReactJS as our project, and would like to start our first contribution with this issue!

@ye1dos
Copy link

ye1dos commented Mar 3, 2022

Hi, I would like to work on this issue. I am taking Open Source course where I must contribute to a project (in my case it is reactJS).

@viveleroi
Copy link

I can reproduce this, and have to find a workaround as it's impacting our application.

It's important to note that the dialog spec confirms that the autofocus attribute must be present for the focus logic to work on dialog open. https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-focusing-steps

Because react has special/custom handling of autofocus it strips the element, although given the dynamic rendering I'm unsure if it would even work.

So far, the only workaround I can find is using setTimeout to run custom focus/blur logic after the dialog/browser 's logic has run.

if (ref.current) {
  const dialog = ref.current
  dialog.showModal()

  setTimeout(() => {
    const focus = dialog.querySelector(':focus')
    if (focus) {
      focus.blur()
    }

    // or call .focus() on something you do want
  })
}

@fidaay
Copy link

fidaay commented Sep 17, 2023

The dialog html element is dead bugged with React 18.2.0, if you try to use useState within a dialog it will just gave you a lot of autoFocus bugs.

@tounsoo
Copy link

tounsoo commented Oct 5, 2023

Any update to this?

@avdb13
Copy link

avdb13 commented Nov 29, 2023

The dialog html element is dead bugged with React 18.2.0, if you try to use useState within a dialog it will just gave you a lot of autoFocus bugs.

And that's why I'm probably not gonna bother anymore with the dialog element. I have been stuck for weeks now trying to debug my input fields which refuse to focus when I click on them.

@tounsoo
Copy link

tounsoo commented Nov 30, 2023

Another bump 👊

@tewarig
Copy link

tewarig commented Dec 21, 2023

bump ++

@tounsoo
Copy link

tounsoo commented Jan 2, 2024

I'm ok if it takes time to fix, but no one responding to this open ticket is very disappointing.

@dan-ville
Copy link

dan-ville commented Jan 29, 2024

Also found the same thing. In a dialog element, if I set the autoFocus prop on a button like this:

 <Button type="button" loading={loading} onClick={onClick} autoFocus>
      Save
    </Button>

It does not work, instead the first focusable element in the dialog receives the autofocus. However, if I set the prop like a normal html prop and use a string

 <Button type="button" loading={loading} onClick={onClick} autofocus="true">
      Save
    </Button>

This works properly, although the React compiler/linting breaks here because obviously that's not how React accepts the prop.

As a workaround, we can use this to manually set the autofocus attribute via DOM APIs.

function MyButton() {
  const ref = useRef<HTMLButtonElement>(null)

  useEffect(() => {
    if (!ref.current) return
    ref.current.setAttribute('autofocus', 'true')
  }, [])
  
  return (
    <Button type="button" loading={loading} onClick={onClick} ref={ref}>
      Save
    </Button>
  )
}

@codebycarlos
Copy link

codebycarlos commented Apr 12, 2024

What worked for me:

  1. Create a ref for the component that needs to be autoFocused.
const autoFocusRef = useRef<HTMLElement>(null);
  1. Manually focus the element whenever the dialog is opened.
const showModal = async (): Promise<void> => {
  dialog.showModal();

  const hasAnimation =
    window.getComputedStyle(dialog).animationName !== "none";

  if (!hasAnimation) {
    autoFocusRef.current?.focus();
    return;
  }

  dialog.addEventListener(
    "animationend",
    () => autoFocusRef.current?.focus(),
    { once: true, passive: true }
  );
};

This accounts for the dialog possibly being animated, which can otherwise also lead to autoFocus not working.

@alaa-m1
Copy link

alaa-m1 commented May 9, 2024

What I am using as a workaround to handle this case with MUI TextField and MUI Dialog is to use a reference "useRef" and then to find the nested "input" element and to apply "focus()" method on it, as the following:

const GenericTextField=({ autoFocus = false }:{ autoFocus?: boolean })=> { 

const inputRef = useRef<HTMLInputElement | null>(null);

  useEffect(() => {
    if (inputRef.current && autoFocus) {
      const inputEl = inputRef?.current?.querySelectorAll?.("input")?.[0];
      if (inputEl) inputEl.focus();
    }
  }, [autoFocus]);


<TextField  ref={ inputRef } …
....
}

@ronaldgj
Copy link

@jantimon until it's not fixed you can try this workaround:

export default function App() {
  const [showDialog1, setShowDialog1] = useState(false);
  const [showDialog2, setShowDialog2] = useState(false);
  return (
    <>
      // ...
      <Dialog open={showDialog2} onClose={() => setShowDialog2(false)}>
        <input value="don't focus on me" />
        <input value="please focus me" autoFocus={showDialog2} />
      </Dialog>
    </>
  );
}

Looks like this works for me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.