Repository: mui/material-ui (TypeScript)
Date: 2019-06-05 13:10:26+00:00
Task type: Bug Fix

Issue:
Popper can't update position when content height changed
<img width="640" alt="屏幕快照 2019-05-23 下午5 33 43" src="https://user-images.githubusercontent.com/6634415/58242214-f6b73b00-7d80-11e9-8b8a-3833686e08a8.png">
<img width="567" alt="屏幕快照 2019-05-23 下午5 33 50" src="https://user-images.githubusercontent.com/6634415/58242223-fc148580-7d80-11e9-84da-2c1d5c018540.png">
<img width="558" alt="屏幕快照 2019-05-23 下午5 35 40" src="https://user-images.githubusercontent.com/6634415/58242326-354cf580-7d81-11e9-931c-08ba7059f180.png">

<img width="1022" alt="屏幕快照 2019-05-23 下午5 34 55" src="https://user-images.githubusercontent.com/6634415/58242283-1babae00-7d81-11e9-9d3a-bf0160db2565.png">

add key prop to Popper``<Popper key={children.length} >``, seems can solve this problem.
Is there a better solution


Issue discussion:
@oyb81076 I had to solve this problem with #15703. I think that we should extract the changes. We should fix this Popper limitation independently from the rewriting the Slider. Would you be interested in isolating the logic with a pull request? 

Solution patch:
diff --git a/packages/material-ui/src/Popper/Popper.d.ts b/packages/material-ui/src/Popper/Popper.d.ts
--- a/packages/material-ui/src/Popper/Popper.d.ts
+++ b/packages/material-ui/src/Popper/Popper.d.ts
@@ -1,5 +1,5 @@
 import * as React from 'react';
-import { ReferenceObject } from 'popper.js';
+import PopperJs, { ReferenceObject } from 'popper.js';
 import { PortalProps } from '../Portal';
 import { TransitionProps } from '../transitions/transition';
 
@@ -18,7 +18,6 @@ export type PopperPlacementType =
   | 'top';
 
 export interface PopperProps extends React.HTMLAttributes<HTMLDivElement> {
-  transition?: boolean;
   anchorEl?: null | ReferenceObject | (() => ReferenceObject);
   children:
     | React.ReactNode
@@ -33,6 +32,8 @@ export interface PopperProps extends React.HTMLAttributes<HTMLDivElement> {
   open: boolean;
   placement?: PopperPlacementType;
   popperOptions?: object;
+  popperRef?: React.Ref<PopperJs>;
+  transition?: boolean;
 }
 
 declare const Popper: React.ComponentType<PopperProps>;
diff --git a/packages/material-ui/src/Popper/Popper.js b/packages/material-ui/src/Popper/Popper.js
--- a/packages/material-ui/src/Popper/Popper.js
+++ b/packages/material-ui/src/Popper/Popper.js
@@ -31,6 +31,10 @@ function getAnchorEl(anchorEl) {
   return typeof anchorEl === 'function' ? anchorEl() : anchorEl;
 }
 
+const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
+
+const defaultPopperOptions = {};
+
 /**
  * Poppers rely on the 3rd party library [Popper.js](https://github.com/FezVrasta/popper.js) for positioning.
  */
@@ -44,15 +48,24 @@ const Popper = React.forwardRef(function Popper(props, ref) {
     modifiers,
     open,
     placement: placementProps = 'bottom',
-    popperOptions = {},
+    popperOptions = defaultPopperOptions,
+    popperRef: popperRefProp,
     transition = false,
     ...other
   } = props;
   const tooltipRef = React.useRef(null);
-  const popperRef = React.useRef();
+  const handleRef = useForkRef(tooltipRef, ref);
+
+  const popperRef = React.useRef(null);
+  const handlePopperRefRef = React.useRef();
+  const handlePopperRef = useForkRef(popperRef, popperRefProp);
+  useEnhancedEffect(() => {
+    handlePopperRefRef.current = handlePopperRef;
+  }, [handlePopperRef]);
+  React.useImperativeHandle(popperRefProp, () => popperRef.current, []);
+
   const [exited, setExited] = React.useState(!props.open);
   const [placement, setPlacement] = React.useState();
-  const handleRef = useForkRef(tooltipRef, ref);
 
   const handleOpen = React.useCallback(() => {
     const handlePopperUpdate = data => {
@@ -69,10 +82,10 @@ const Popper = React.forwardRef(function Popper(props, ref) {
 
     if (popperRef.current) {
       popperRef.current.destroy();
-      popperRef.current = null;
+      handlePopperRefRef.current(null);
     }
 
-    popperRef.current = new PopperJS(getAnchorEl(anchorEl), popperNode, {
+    const popper = new PopperJS(getAnchorEl(anchorEl), popperNode, {
       placement: flipPlacement(placementProps),
       ...popperOptions,
       modifiers: {
@@ -92,6 +105,7 @@ const Popper = React.forwardRef(function Popper(props, ref) {
       onCreate: createChainedFunction(handlePopperUpdate, popperOptions.onCreate),
       onUpdate: createChainedFunction(handlePopperUpdate, popperOptions.onUpdate),
     });
+    handlePopperRefRef.current(popper);
   }, [anchorEl, disablePortal, modifiers, open, placement, placementProps, popperOptions]);
 
   const handleEnter = () => {
@@ -104,7 +118,7 @@ const Popper = React.forwardRef(function Popper(props, ref) {
     }
 
     popperRef.current.destroy();
-    popperRef.current = null;
+    handlePopperRefRef.current(null);
   };
 
   const handleExited = () => {
@@ -270,6 +284,10 @@ Popper.propTypes = {
    * Options provided to the [`popper.js`](https://github.com/FezVrasta/popper.js) instance.
    */
   popperOptions: PropTypes.object,
+  /**
+   * Callback fired when a new popper instance is used.
+   */
+  popperRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
   /**
    * Help supporting a react-transition-group/Transition component.
    */
diff --git a/packages/material-ui/src/Popper/Popper.spec.tsx b/packages/material-ui/src/Popper/Popper.spec.tsx
new file mode 100644
--- /dev/null
+++ b/packages/material-ui/src/Popper/Popper.spec.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import Tooltip from '@material-ui/core/Tooltip';
+import PopperJs from 'popper.js';
+
+interface Props {
+  children: React.ReactElement;
+  value: number;
+}
+
+export default function ThumbLabelComponent(props: Props) {
+  const { children, value } = props;
+
+  const popperRef = React.useRef<PopperJs | null>(null);
+  React.useEffect(() => {
+    if (popperRef.current) {
+      popperRef.current.update();
+    }
+  });
+
+  return (
+    <Tooltip
+      PopperProps={{
+        popperRef,
+      }}
+      enterTouchDelay={0}
+      placement="top"
+      title={value}
+    >
+      {children}
+    </Tooltip>
+  );
+}
diff --git a/packages/material-ui/src/Tooltip/Tooltip.d.ts b/packages/material-ui/src/Tooltip/Tooltip.d.ts
--- a/packages/material-ui/src/Tooltip/Tooltip.d.ts
+++ b/packages/material-ui/src/Tooltip/Tooltip.d.ts
@@ -1,6 +1,7 @@
 import * as React from 'react';
 import { StandardProps } from '..';
 import { TransitionProps } from '../transitions/transition';
+import { PopperProps } from '../Popper/Popper';
 
 export interface TooltipProps
   extends StandardProps<React.HTMLAttributes<HTMLDivElement>, TooltipClassKey, 'title', false> {
@@ -30,7 +31,7 @@ export interface TooltipProps
     | 'top-end'
     | 'top-start'
     | 'top';
-  PopperProps?: object;
+  PopperProps?: Partial<PopperProps>;
   title: React.ReactNode;
   TransitionComponent?: React.ComponentType<TransitionProps>;
   TransitionProps?: TransitionProps;
diff --git a/pages/api/popper.md b/pages/api/popper.md
--- a/pages/api/popper.md
+++ b/pages/api/popper.md
@@ -27,6 +27,7 @@ Poppers rely on the 3rd party library [Popper.js](https://github.com/FezVrasta/p
 | <span class="prop-name required">open&nbsp;*</span> | <span class="prop-type">bool</span> |  | If `true`, the popper is visible. |
 | <span class="prop-name">placement</span> | <span class="prop-type">enum:&nbsp;'bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top'<br></span> | <span class="prop-default">'bottom'</span> | Popper placement. |
 | <span class="prop-name">popperOptions</span> | <span class="prop-type">object</span> | <span class="prop-default">{}</span> | Options provided to the [`popper.js`](https://github.com/FezVrasta/popper.js) instance. |
+| <span class="prop-name">popperRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br></span> |  | Callback fired when a new popper instance is used. |
 | <span class="prop-name">transition</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Help supporting a react-transition-group/Transition component. |
 
 The `ref` is forwarded to the root element.


Test patch:
diff --git a/packages/material-ui/src/Popper/Popper.test.js b/packages/material-ui/src/Popper/Popper.test.js
--- a/packages/material-ui/src/Popper/Popper.test.js
+++ b/packages/material-ui/src/Popper/Popper.test.js
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
 import { createMount } from '@material-ui/core/test-utils';
 import describeConformance from '@material-ui/core/test-utils/describeConformance';
 import consoleErrorMock from 'test/utils/consoleErrorMock';
+import PopperJS from 'popper.js';
 import Grow from '../Grow';
 import Popper from './Popper';
 
@@ -203,6 +204,20 @@ describe('<Popper />', () => {
     });
   });
 
+  describe('prop: popperRef', () => {
+    it('should return a ref', () => {
+      const ref1 = React.createRef();
+      const ref2 = React.createRef();
+      const wrapper = mount(<Popper {...defaultProps} popperRef={ref1} />);
+      assert.strictEqual(ref1.current instanceof PopperJS, true);
+      wrapper.setProps({
+        popperRef: ref2,
+      });
+      assert.strictEqual(ref1.current, null);
+      assert.strictEqual(ref2.current instanceof PopperJS, true);
+    });
+  });
+
   describe('warnings', () => {
     beforeEach(() => {
       consoleErrorMock.spy();