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

Components: refactor Navigation to pass exhaustive-deps #41612

Merged
merged 7 commits into from
Jul 12, 2022
Prev Previous commit
Next Next commit
fix Controlled state by ensuring dependency Ref is kept in sync with …
…state
  • Loading branch information
chad1008 authored and ciampo committed Jul 12, 2022
commit 3473d5d96ddfc3a1194968e36740b0e39f7e8250
22 changes: 13 additions & 9 deletions packages/components/src/navigation/index.js
Original file line number Diff line number Diff line change
@@ -6,7 +6,12 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useEffect, useRef, useState } from '@wordpress/element';
import {
useEffect,
useLayoutEffect,
useRef,
useState,
} from '@wordpress/element';
import { isRTL } from '@wordpress/i18n';

/**
@@ -28,7 +33,6 @@ export default function Navigation( {
onActivateMenu = noop,
} ) {
const [ menu, setMenu ] = useState( activeMenu );
const menuRef = useRef( activeMenu );
const [ slideOrigin, setSlideOrigin ] = useState();
const navigationTree = useCreateNavigationTree();
const defaultSlideOrigin = isRTL() ? 'right' : 'left';
@@ -51,15 +55,15 @@ export default function Navigation( {
}
}, [] );

useEffect( () => {
menuRef.current = menu;
}, [ menu ] );
// Used to prevent excessive useEffect fires when navigation is being controlled by parent component
const controlledMenuUpdate = useRef( { setActiveMenu, menu } );
useLayoutEffect( () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Any particular reason why you opted for useLayoutEffect instead of useEffect ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to make sure it's always up to date.. If I'd used a useEffect here, and then some other future change tried to use this hook from a useLayoutEffect, that would be a stale reference because layoutEffects fire first.

Updating the ref with useLayoutEffect here should ensure it's always up to date, regardless of what kind of effect tries to call it later.

controlledMenuUpdate.current = { setActiveMenu, menu };
} );

// Used to prevent resetting the `menu` state when navigating between menus
const setActiveMenuRef = useRef( setActiveMenu );
useEffect( () => {
if ( activeMenu !== menuRef.current ) {
setActiveMenuRef.current( activeMenu );
if ( activeMenu !== controlledMenuUpdate.current.menu ) {
controlledMenuUpdate.current.setActiveMenu( activeMenu );
}
}, [ activeMenu ] );