Skip to content

Commit

Permalink
I think I've done this right
Browse files Browse the repository at this point in the history
  • Loading branch information
joshwooding committed Apr 9, 2019
1 parent c4f745a commit 17762f2
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 20 deletions.
30 changes: 24 additions & 6 deletions packages/material-ui/src/Tooltip/Tooltip.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import warning from 'warning';
import clsx from 'clsx';
import RootRef from '../RootRef';
import { fade } from '../styles/colorManipulator';
import withStyles from '../styles/withStyles';
import { capitalize } from '../utils/helpers';
import Grow from '../Grow';
import Popper from '../Popper';
import { useForkRef } from '../utils/reactHelpers';

export const styles = theme => ({
/* Styles applied to the Popper component. */
Expand Down Expand Up @@ -72,6 +73,18 @@ export const styles = theme => ({
},
});

function useMountedRef() {
const mountedRef = React.useRef(false);
React.useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);

return mountedRef;
}

function Tooltip(props) {
const {
children,
Expand Down Expand Up @@ -100,8 +113,13 @@ function Tooltip(props) {
const ignoreNonTouchEvents = React.useRef(false);
const { current: isControlled } = React.useRef(props.open != null);
const childrenRef = React.useRef();
// can be removed once we drop support for non ref forwarding class components
const handleOwnRef = React.useCallback(ref => {
childrenRef.current = ReactDOM.findDOMNode(ref);
}, []);
const handleRef = useForkRef(children.ref, handleOwnRef);
const defaultId = React.useRef();
const isMounted = React.useRef(false);
const mountedRef = useMountedRef();
const closeTimer = React.useRef();
const enterTimer = React.useRef();
const leaveTimer = React.useRef();
Expand Down Expand Up @@ -129,11 +147,11 @@ function Tooltip(props) {
defaultId.current = `mui-tooltip-${Math.round(Math.random() * 1e5)}`;

// Rerender with defaultId and childrenRef.
if (openProp && !isMounted.current) {
if (openProp && !mountedRef.current) {
forceUpdate();
}
isMounted.current = true;
}, [title, openProp]);
mountedRef.current = true;
}, [title, openProp, mountedRef]);

React.useEffect(() => {
return () => {
Expand Down Expand Up @@ -324,7 +342,7 @@ function Tooltip(props) {

return (
<React.Fragment>
<RootRef rootRef={childrenRef}>{React.cloneElement(children, childrenProps)}</RootRef>
{React.cloneElement(children, { ref: handleRef, ...childrenProps })}
<Popper
className={clsx(classes.popper, {
[classes.popperInteractive]: interactive,
Expand Down
28 changes: 14 additions & 14 deletions packages/material-ui/src/Tooltip/Tooltip.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Popper from '../Popper';
import Tooltip from './Tooltip';
import Input from '../Input';
import createMuiTheme from '../styles/createMuiTheme';
import RootRef from '../RootRef';

const theme = createMuiTheme();

Expand All @@ -17,7 +16,7 @@ describe('<Tooltip />', () => {
let classes;
let clock;
const defaultProps = {
children: <span>Hello World</span>,
children: <span id="testChild">Hello World</span>,
theme,
title: 'Hello World',
};
Expand All @@ -36,7 +35,6 @@ describe('<Tooltip />', () => {
it('should render the correct structure', () => {
const wrapper = mount(<Tooltip {...defaultProps} />);
const children = wrapper.childAt(0);
assert.strictEqual(children.childAt(0).type(), RootRef);
assert.strictEqual(children.childAt(1).type(), Popper);
assert.strictEqual(children.childAt(1).hasClass(classes.popper), true);
});
Expand Down Expand Up @@ -86,7 +84,7 @@ describe('<Tooltip />', () => {

it('should respond to external events', () => {
const wrapper = mount(<Tooltip {...defaultProps} />);
const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
assert.strictEqual(wrapper.find(Popper).props().open, false);
children.simulate('mouseOver');
assert.strictEqual(wrapper.find(Popper).props().open, true);
Expand All @@ -101,7 +99,7 @@ describe('<Tooltip />', () => {
const wrapper = mount(
<Tooltip {...defaultProps} open onOpen={handleRequestOpen} onClose={handleClose} />,
);
const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
assert.strictEqual(handleRequestOpen.callCount, 0);
assert.strictEqual(handleClose.callCount, 0);
children.simulate('mouseOver');
Expand All @@ -114,7 +112,7 @@ describe('<Tooltip />', () => {

it('should close when the interaction is over', () => {
const wrapper = mount(<Tooltip {...defaultProps} />);
const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
assert.strictEqual(wrapper.find(Popper).props().open, false);
children.simulate('mouseOver');
children.simulate('focus');
Expand All @@ -128,7 +126,7 @@ describe('<Tooltip />', () => {
describe('touch screen', () => {
it('should not respond to quick events', () => {
const wrapper = mount(<Tooltip {...defaultProps} />);
const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
children.simulate('touchStart');
children.simulate('touchEnd');
children.simulate('focus');
Expand All @@ -137,7 +135,7 @@ describe('<Tooltip />', () => {

it('should open on long press', () => {
const wrapper = mount(<Tooltip {...defaultProps} />);
const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
children.simulate('touchStart');
children.simulate('focus');
clock.tick(1000);
Expand Down Expand Up @@ -181,7 +179,7 @@ describe('<Tooltip />', () => {
describe('prop: delay', () => {
it('should take the enterDelay into account', () => {
const wrapper = mount(<Tooltip enterDelay={111} {...defaultProps} />);
const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
children.simulate('focus');
assert.strictEqual(wrapper.find(Popper).props().open, false);
clock.tick(111);
Expand All @@ -191,7 +189,7 @@ describe('<Tooltip />', () => {

it('should take the leaveDelay into account', () => {
const wrapper = mount(<Tooltip leaveDelay={111} {...defaultProps} />);
const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
children.simulate('focus');
clock.tick(0);
assert.strictEqual(wrapper.find(Popper).props().open, true);
Expand All @@ -217,12 +215,12 @@ describe('<Tooltip />', () => {
const handler = spy();
const wrapper = mount(
<Tooltip {...defaultProps} title="Hello World">
<button type="submit" {...{ [name]: handler }}>
<button id="testChild" type="submit" {...{ [name]: handler }}>
Hello World
</button>
</Tooltip>,
);
const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
const type = name.slice(2).toLowerCase();
children.simulate(type);
clock.tick(0);
Expand Down Expand Up @@ -271,11 +269,13 @@ describe('<Tooltip />', () => {
it('should keep the overlay open if the popper element is hovered', () => {
const wrapper = mount(
<Tooltip title="Hello World" interactive leaveDelay={111}>
<button type="submit">Hello World</button>
<button id="testChild" type="submit">
Hello World
</button>
</Tooltip>,
);

const children = wrapper.childAt(0).childAt(0);
const children = wrapper.find('#testChild');
children.simulate('mouseOver', { type: 'mouseOver' });
clock.tick(0);
assert.strictEqual(wrapper.find(Popper).props().open, true);
Expand Down

0 comments on commit 17762f2

Please sign in to comment.