Solution to the problem discussed with Dan Abramov here.
implementation:
export default function useQueueFocus(): (elementRef: React.MutableRefObject<HTMLElement | null>) => void {
const isUnmountedRef = useRef(false)
const forceUpdate = useForceUpdate()
const ref = useRef<MutableRefObject<HTMLElement | null> | undefined>(undefined)
useLayoutEffect(() => {
return (): void => {
if (isUnmountedRef.current) {
return
}
// eslint-disable-next-line no-restricted-syntax
ref.current?.current?.focus()
isUnmountedRef.current = true
ref.current = undefined
}
}, [])
useLayoutEffect(() => {
// eslint-disable-next-line no-restricted-syntax
ref.current?.current?.focus()
ref.current = undefined
})
return useCallback(
(elementRef: MutableRefObject<HTMLElement | null>) => {
ref.current = elementRef
if (isUnmountedRef.current) {
// eslint-disable-next-line no-restricted-syntax
elementRef.current?.focus()
} else {
forceUpdate()
}
},
[forceUpdate],
)
}
.eslintrc.js
:
module.exports = {
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'MemberExpression[property.name="focus"][object.property.name="current"]',
message: `Don't call focus() directly on an element. Use queueFocus() instead.`,
},
{
selector: 'OptionalMemberExpression[property.name="focus"][object.property.name="current"]',
message: `Don't call focus() directly on an element. Use queueFocus() instead.`,
},
]
}
}