Skip to content

Instantly share code, notes, and snippets.

@astoilkov
Last active January 14, 2023 05:55
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save astoilkov/5d7b493634586a48d2f1c336349af9d8 to your computer and use it in GitHub Desktop.
Save astoilkov/5d7b493634586a48d2f1c336349af9d8 to your computer and use it in GitHub Desktop.

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