Repository: uber-web/baseui (js)

Issue:
Popovers violate invariant rendering after ssr
<!--- Provide a general summary of the issue in the Title above -->
I pulled in the latest alpha of [fusion-react](https://github.com/fusionjs/fusion-react/releases/tag/v2.0.0-alpha.2), and was testing out baseui components which are server side rendered, but the popover component was throwing invariant violations, `Invariant Violation: Portals are not currently supported by the server renderer. Render them conditionally so that they only appear on the client render.`:

![image](https://user-images.githubusercontent.com/877194/53689350-6e3c7780-3d08-11e9-8383-1e56ed7512c8.png)


## Current Behavior
Popovers initially ssr rendered with the following code will error

```
<Popover isOpen={true} content={...}>{...}</Popover>
```
<!---
    Describe what happens instead of the expected behavior.
-->

## Expected Behavior
Component renders without error
<!---
    Describe what should happen.
-->

## Current Workaround
```
function ExComp() {
  // hooks way of `componentDidMount`
  const [initialized, setInitialized] = useState(false);
  useEffect(() => {
    setInitialized(true);
  });

  return <Popover isOpen={initialized}content={...}>{...}</Popover>
}
```
which still throws warnings...
![image](https://user-images.githubusercontent.com/877194/53689452-37b42c00-3d0b-11e9-9916-0d08eb143e96.png)

## Your Environment

<!--- Include as many relevant details about the environment with which you experienced the bug. -->
```
❯ npm ls react-dom
└── react-dom@16.8.3
❯ npm ls baseui
└── baseui@5.14.0
```


To get the same env, run a [fusion.js](https://fusionjs.com/) app. Add baseui of course, and then either build yourself the latest alpha of [fusion-react](https://github.com/fusionjs/fusion-react/releases/tag/v2.0.0-alpha.2)  or just alias the following forked & scoped package which includes it...
```
yarn add fusion-apollo@npm:@enjoylife/fusion-apollo # to get the alpha dist of fusion-react i'm usin
```

- [x ] I have searched the [issues](https://github.com/uber-web/baseui/issues) of this repository and believe that this is not a duplicate.


Pull request description:
fix(popover): render without error after ssr
We have to check for mount as the call to createPortal will error with `Portals are not currently supported by the server renderer...` if rehydrating after ssr. See, https://github.com/facebook/react/issues/13097#issuecomment-405658104,  for rationale of the given changes.

Fixes #976

#### Description

<!-- Describe your changes below in as much detail as possible -->

#### Scope

- [x ] Patch: Bug Fix
- [ ] Minor: New Feature
- [ ] Major: Breaking Change


Patch:
diff --git a/src/popover/popover.js b/src/popover/popover.js
index 06a0d4822..4b2dc9682 100644
--- a/src/popover/popover.js
+++ b/src/popover/popover.js
@@ -57,15 +57,18 @@ class Popover extends React.Component<PopoverPropsT, PopoverPrivateStateT> {
   state = this.getDefaultState(this.props);
 
   componentDidMount() {
-    if (this.props.isOpen) {
-      this.initializePopper();
-      this.addDomEvents();
-    }
     this.generatedId = getBuiId();
+    this.setState({isMounted: true});
   }
 
-  componentDidUpdate(prevProps: PopoverPropsT) {
-    if (this.props.isOpen !== prevProps.isOpen) {
+  componentDidUpdate(
+    prevProps: PopoverPropsT,
+    prevState: PopoverPrivateStateT,
+  ) {
+    if (
+      this.props.isOpen !== prevProps.isOpen ||
+      this.state.isMounted !== prevState.isMounted
+    ) {
       if (this.props.isOpen) {
         // Clear any existing timers (like previous animateOutCompleteTimer)
         this.clearTimers();
@@ -93,6 +96,7 @@ class Popover extends React.Component<PopoverPropsT, PopoverPrivateStateT> {
       arrowOffset: {left: 0, top: 0},
       popoverOffset: {left: 0, top: 0},
       placement: props.placement,
+      isMounted: false,
     };
   }
 
@@ -468,7 +472,10 @@ class Popover extends React.Component<PopoverPropsT, PopoverPrivateStateT> {
 
     // Only render popover on the browser (portals aren't supported server-side)
     if (__BROWSER__) {
-      if (this.props.isOpen || this.state.isAnimating) {
+      if (
+        this.state.isMounted &&
+        (this.props.isOpen || this.state.isAnimating)
+      ) {
         rendered.push(
           // $FlowFixMe
           ReactDOM.createPortal(this.renderPopover(), document.body),
diff --git a/src/popover/types.js b/src/popover/types.js
index ab04325d9..0676620c0 100644
--- a/src/popover/types.js
+++ b/src/popover/types.js
@@ -168,6 +168,7 @@ export type PopoverPrivateStateT = {
   arrowOffset: OffsetT,
   popoverOffset: OffsetT,
   placement: PopoverPlacementT,
+  isMounted: boolean,
 };
 
 export type SharedStylePropsArgT = {


Test patch:
diff --git a/src/popover/__tests__/__snapshots__/popover.test.js.snap b/src/popover/__tests__/__snapshots__/popover.test.js.snap
index 2d91b9987..225bbba29 100644
--- a/src/popover/__tests__/__snapshots__/popover.test.js.snap
+++ b/src/popover/__tests__/__snapshots__/popover.test.js.snap
@@ -37,7 +37,7 @@ Object {
     "current": null,
   },
   "$showArrow": true,
-  "id": null,
+  "id": "bui-mock-id",
   "onMouseEnter": [Function],
   "onMouseLeave": [Function],
 }
diff --git a/src/popover/__tests__/popover.test.js b/src/popover/__tests__/popover.test.js
index bc5e727ce..8178dc38c 100644
--- a/src/popover/__tests__/popover.test.js
+++ b/src/popover/__tests__/popover.test.js
@@ -221,8 +221,9 @@ describe('Popover', () => {
       </Popover>,
     );
 
-    expect(CustomComponent).toHaveBeenCalledTimes(1);
-    expect(CustomComponent).toHaveBeenLastCalledWith(
+    // first render is prior to mount, second is after cdm
+    expect(CustomComponent).toHaveBeenCalledTimes(2);
+    expect(CustomComponent).toHaveBeenCalledWith(
       {
         $ref: wrapper.instance().anchorRef,
         onClick: wrapper.instance().onAnchorClick,
@@ -232,6 +233,16 @@ describe('Popover', () => {
       },
       {},
     );
+    expect(CustomComponent).toHaveBeenLastCalledWith(
+      {
+        $ref: wrapper.instance().anchorRef,
+        onClick: wrapper.instance().onAnchorClick,
+        'aria-controls': 'bui-mock-id',
+        'aria-haspopup': 'true',
+        'aria-expanded': 'true',
+      },
+      {},
+    );
   });
 
   test('component overrides', () => {