Repository: mui/material-ui (TypeScript)
Date: 2019-04-15 20:10:29+00:00
Task type: Bug Fix

Issue:
[Popper] onUpdate / onCreate not handled
`onUpdate` and `onCreate` options are overridden in the Popper component, making it difficult to access internal data about the popout / get lifecycle callbacks.

<!--
    Thank you very much for contributing to Material-UI by creating an issue! ❤️
    To avoid duplicate issues we ask you to check off the following list.
-->

<!-- Checked checkbox should look like this: [x] -->
- [x] This is not a v0.x issue. <!-- (v0.x is no longer maintained) -->
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Expected Behavior 🤔
The `popperOptions` should support `onUpdate` and `onCreate` as per docs (https://github.com/FezVrasta/popper.js#callbacks). i.e. Popper callbacks should be fired when PopperJS creates or "updates" a popup.

## Current Behavior 😯
The callbacks do not fire, as they are overridden in the wrapping MUI Popper component.

## Steps to Reproduce 🕹
<!---
    Provide a link to a live example (you can use codesandbox.io) and an unambiguous set of steps to reproduce this bug.
    Include code to reproduce, if relevant (which it most likely is).

    This codesandbox.io template _may_ be a good starting point:
    https://codesandbox.io/s/github/mui-org/material-ui/tree/next/examples/create-react-app

    If you're using typescript a better starting point would be
    https://codesandbox.io/s/github/mui-org/material-ui/tree/next/examples/create-react-app-with-typescript

    If YOU DO NOT take time to provide a codesandbox.io reproduction, should the COMMUNITY take time to help you?

-->
Try to use `onUpdate` or `onCreate` options: i.e. `<Popper popperOptions={{ onUpdate: data => console.log(data) }} />`

Link:

1. https://codesandbox.io/s/822z0prqz2

## Context 🔦
I am trying to get a callback when the popup is created and get the dimensions of the rendered component.

## Your Environment 🌎
<!---
    Include as many relevant details about the environment with which you experienced the bug.
    If you encounter issues with typescript please include version and tsconfig.
-->

| Tech         | Version |
|--------------|---------|
| Material-UI  | v3.6.0  |
| React        |   v16.8.1      |
| Browser      |    Chrome     |
| TypeScript   |     No    |



Issue discussion:
> I am trying to get a callback when the popup is created and get the dimensions of the rendered component.

Have you tried to provide a top div and to get a reference on it? 
> > I am trying to get a callback when the popup is created and get the dimensions of the rendered component.
> 
> Have you tried to provide a top div and to get a reference on it?

Thanks for the reply! What I'm trying to achieve is integration of Popper on a desktop application - which means resizing/positioning a native dialog (in place of a DOM element) whenever certain events occur (creation / reposition). But these callback options have been excluded from the public popperOptions API.

Although, your comment got me digging a bit and `innerRef` might help. I'll report back if that works! Thanks again.
@jaipe Let us know :). If it's not enough. I have no objection with fixing the `onCreate`/`onUpdate` shallowing. We have a function helper that we can use:
```diff
--- a/packages/material-ui/src/Popper/Popper.js
+++ b/packages/material-ui/src/Popper/Popper.js
@@ -4,6 +4,7 @@ import PopperJS from 'popper.js';
 import { chainPropTypes } from '@material-ui/utils';
 import Portal from '../Portal';
 import { setRef, withForwardedRef } from '../utils';
+import { createChainedFunction } from '../utils/helpers';

 function flipPlacement(placement) {
   const direction = (typeof window !== 'undefined' && document.body.getAttribute('dir')) || 'ltr';
@@ -113,8 +114,8 @@ class Popper extends React.Component {
       },
       // We could have been using a custom modifier like react-popper is doing.
       // But it seems this is the best public API for this use case.
-      onCreate: this.handlePopperUpdate,
-      onUpdate: this.handlePopperUpdate,
+      onCreate: createChainedFunction(this.handlePopperUpdate, popperOptions.onCreate),
+      onUpdate: createChainedFunction(this.handlePopperUpdate, popperOptions.onUpdate),
     });
   };
```
Gave it a shot with an outer ref and the inner ref. It seems the outer ref is resolving before the popper content has been set.

I'm happy to give your above fix a run and add some tests, if you're happy for a fix to go in! :)
@jaipe No objection :)

Solution patch:
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
@@ -4,6 +4,7 @@ import PopperJS from 'popper.js';
 import { chainPropTypes } from '@material-ui/utils';
 import Portal from '../Portal';
 import { setRef, withForwardedRef } from '../utils';
+import { createChainedFunction } from '../utils/helpers';
 
 function flipPlacement(placement) {
   const direction = (typeof window !== 'undefined' && document.body.getAttribute('dir')) || 'ltr';
@@ -96,8 +97,8 @@ class Popper extends React.Component {
       },
       // We could have been using a custom modifier like react-popper is doing.
       // But it seems this is the best public API for this use case.
-      onCreate: this.handlePopperUpdate,
-      onUpdate: this.handlePopperUpdate,
+      onCreate: createChainedFunction(this.handlePopperUpdate, popperOptions.onCreate),
+      onUpdate: createChainedFunction(this.handlePopperUpdate, popperOptions.onUpdate),
     });
   };
 


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
@@ -123,6 +123,20 @@ describe('<Popper />', () => {
     });
   });
 
+  describe('prop: popperOptions', () => {
+    it('should pass all popperOptions to popperjs', done => {
+      const popperOptions = {
+        onCreate: data => {
+          data.instance.update({ placement: 'left' });
+        },
+        onUpdate: () => {
+          done();
+        },
+      };
+      mount(<Popper {...defaultProps} popperOptions={popperOptions} placement="top" open />);
+    });
+  });
+
   describe('prop: keepMounted', () => {
     describe('by default', () => {
       // Test case for https://github.com/mui-org/material-ui/issues/15180