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

Fix HMR errors #5209

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-turtles-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': patch
---

Check certain refs for nullishness to address HMR issues in dotcom
4 changes: 3 additions & 1 deletion packages/react/src/ActionMenu/ActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,9 @@ const Overlay: React.FC<React.PropsWithChildren<MenuOverlayProps>> = ({
// If the menu anchor is an icon button, we need to label the menu by tooltip that also labelled the anchor.
const [anchorAriaLabelledby, setAnchorAriaLabelledby] = useState<null | string>(null)
useEffect(() => {
if (anchorRef.current) {
// Necessary for HMR reloads
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (anchorRef?.current) {
Copy link
Member

@joshblack joshblack Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, do we know why this ref is undefined when doing our upstream work in dotcom? 🤔 Specifically for HMR. It seems like something is happening with the context value being passed along that's different from how it's being represented in TS?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We think it's because the DOM is being replaced, but honestly that's just a theory 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like @camertron said, we think the DOM is being rebuilt and inserted, which temporarily seems to make the refs undefined. However, it's barely undefined long enough to matter. By the time the page renders, everything is rehydrated and all works fine. But for that split second while it's being swapped out, React throws a fit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have thought the ref would be stable since it ultimately is coming from useRef and the value returned from that wouldn't be undefined between renders 🤔 (although current totally would if the DOM is changing)

Could it be that the context value (MenuContext) is changing during HMR? It seems like we're explicitly casting this value as being defined in:

} = React.useContext(MenuContext) as MandateProps<MenuContextProps, 'anchorRef'>
but with the HMR stuff here that doesn't seem to be the case

const ariaLabelledby = anchorRef.current.getAttribute('aria-labelledby')
if (ariaLabelledby) {
setAnchorAriaLabelledby(ariaLabelledby)
Expand Down
10 changes: 5 additions & 5 deletions packages/react/src/hooks/useMenuInitialFocus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {iterateFocusableElements} from '@primer/behaviors/utils'

export const useMenuInitialFocus = (
open: boolean,
containerRef: React.RefObject<HTMLElement>,
anchorRef: React.RefObject<HTMLElement>,
containerRef?: React.RefObject<HTMLElement>,
anchorRef?: React.RefObject<HTMLElement>,
) => {
/**
* We need to pick the first element to focus based on how the menu was opened,
Expand All @@ -15,7 +15,7 @@ export const useMenuInitialFocus = (

React.useEffect(
function inferOpeningKey() {
const anchorElement = anchorRef.current
const anchorElement = anchorRef?.current

const clickHandler = (event: MouseEvent) => {
// event.detail === 0 means onClick was fired by Enter/Space key
Expand Down Expand Up @@ -46,12 +46,12 @@ export const useMenuInitialFocus = (
*/
React.useEffect(
function moveFocusOnOpen() {
if (!open || !containerRef.current) return // wait till the menu is open
if (!open || !containerRef?.current) return // wait till the menu is open

const iterable = iterateFocusableElements(containerRef.current)

if (openingGesture === 'mouse-click') {
if (anchorRef.current) anchorRef.current.focus()
if (anchorRef?.current) anchorRef.current.focus()
else throw new Error('For focus management, please attach anchorRef')
} else if (openingGesture && ['ArrowDown', 'Space', 'Enter'].includes(openingGesture)) {
const firstElement = iterable.next().value
Expand Down
26 changes: 13 additions & 13 deletions packages/react/src/hooks/useMenuKeyboardNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import type {MenuCloseHandler} from '../ActionMenu'
export const useMenuKeyboardNavigation = (
open: boolean,
onClose: MenuCloseHandler | undefined,
containerRef: React.RefObject<HTMLElement>,
anchorRef: React.RefObject<HTMLElement>,
isSubmenu: boolean,
containerRef?: React.RefObject<HTMLElement>,
anchorRef?: React.RefObject<HTMLElement>,
isSubmenu: boolean = false,
) => {
useMenuInitialFocus(open, containerRef, anchorRef)
useMnemonics(open, containerRef)
Expand All @@ -32,12 +32,12 @@ export const useMenuKeyboardNavigation = (
const useCloseMenuOnTab = (
open: boolean,
onClose: MenuCloseHandler | undefined,
containerRef: React.RefObject<HTMLElement>,
anchorRef: React.RefObject<HTMLElement>,
containerRef?: React.RefObject<HTMLElement>,
anchorRef?: React.RefObject<HTMLElement>,
) => {
React.useEffect(() => {
const container = containerRef.current
const anchor = anchorRef.current
const container = containerRef?.current
const anchor = anchorRef?.current

const handler = (event: KeyboardEvent) => {
if (open && event.key === 'Tab') onClose?.('tab')
Expand All @@ -59,10 +59,10 @@ const useCloseSubmenuOnArrow = (
open: boolean,
isSubmenu: boolean,
onClose: MenuCloseHandler | undefined,
containerRef: React.RefObject<HTMLElement>,
containerRef?: React.RefObject<HTMLElement>,
) => {
React.useEffect(() => {
const container = containerRef.current
const container = containerRef?.current

const handler = (event: KeyboardEvent) => {
if (open && isSubmenu && event.key === 'ArrowLeft') onClose?.('arrow-left')
Expand All @@ -81,12 +81,12 @@ const useCloseSubmenuOnArrow = (
*/
const useMoveFocusToMenuItem = (
open: boolean,
containerRef: React.RefObject<HTMLElement>,
anchorRef: React.RefObject<HTMLElement>,
containerRef?: React.RefObject<HTMLElement>,
anchorRef?: React.RefObject<HTMLElement>,
) => {
React.useEffect(() => {
const container = containerRef.current
const anchor = anchorRef.current
const container = containerRef?.current
const anchor = anchorRef?.current

const handler = (event: KeyboardEvent) => {
if (!open || !container) return
Expand Down
Loading