Skip to content

Instantly share code, notes, and snippets.

@astoilkov
Last active May 1, 2022
Embed
What would you like to do?

Batching makes it difficult to perform imperative actions like focus

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.`,
      },
    ]
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment