react
react copied to clipboard
Bug: autoFocus broken inside <dialog />
React version: 17 and 18.0.0-rc.0-next-27b569969-20220211
Steps To Reproduce
- render
<input /><input autoFocus />
inside<dialog />
- execute the
showModal()
method of the dialog - you will notice that react will not set focus to the correct input element
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 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>
</>
);
}
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!
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).
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
})
}
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.
Any update to this?
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.
Another bump 👊
bump ++
I'm ok if it takes time to fix, but no one responding to this open ticket is very disappointing.
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>
)
}
What worked for me:
- Create a ref for the component that needs to be autoFocused.
const autoFocusRef = useRef<HTMLElement>(null);
- 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.
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 } …
....
}