Skip to content

Commit

Permalink
[Slide] Convert to function component (#15344)
Browse files Browse the repository at this point in the history
* [Slide] Convert to function component

* Remove innerRef propType from Table

* Handle change in style formatting in tests

* Code review

* Code review v2

* Fix tests

* Add useCallback to handleOwnRef

* Prettier

* Add breaking change to migration docs
  • Loading branch information
joshwooding authored and oliviertassinari committed Apr 19, 2019
1 parent ccc466a commit 19d418d
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 255 deletions.
18 changes: 17 additions & 1 deletion docs/src/pages/guides/migration-v3/migration-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,26 @@ You should be able to move the custom styles to the root class key.
Only special HTML elements have these default behaviors.
People should use `event.stopPropagation()` if they don't want to trigger a `onClose` event on the modal.

### Slide

- [Slide] The child needs to be able to hold a ref.

```diff
class Component extends React.Component {
render() {
return <div />
}
}
-const MyComponent = props => <div {...props} />
+const MyComponent = React.forwardRef((props, ref) => <div ref={ref} {...props} />);
<Slide><Component /></Slide>
<Slide><MyComponent /></Slide>
<Slide><div /></Slide>

### Tooltip

- [Tooltip] The child needs to be able to hold a ref.

```diff
class Component extends React.Component {
render() {
Expand Down
238 changes: 113 additions & 125 deletions packages/material-ui/src/Slide/Slide.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import EventListener from 'react-event-listener';
import debounce from 'debounce'; // < 1kb payload overhead when lodash/debounce is > 3kb.
import { Transition } from 'react-transition-group';
import { setRef } from '../utils/reactHelpers';
import { useForkRef } from '../utils/reactHelpers';
import withTheme from '../styles/withTheme';
import { duration } from '../styles/transitions';
import { reflow, getTransitionProps } from '../transitions/utils';
Expand All @@ -16,8 +15,7 @@ const GUTTER = 24;
// Translate the node so he can't be seen on the screen.
// Later, we gonna translate back the node to his original location
// with `translate3d(0, 0, 0)`.`
function getTranslateValue(props, node) {
const { direction } = props;
function getTranslateValue(direction, node) {
const rect = node.getBoundingClientRect();

let transform;
Expand Down Expand Up @@ -59,8 +57,8 @@ function getTranslateValue(props, node) {
return `translateY(-${rect.top + rect.height + GUTTER - offsetY}px)`;
}

export function setTranslateValue(props, node) {
const transform = getTranslateValue(props, node);
export function setTranslateValue(direction, node) {
const transform = getTranslateValue(direction, node);

if (transform) {
node.style.webkitTransform = transform;
Expand All @@ -72,65 +70,49 @@ export function setTranslateValue(props, node) {
* The Slide transition is used by the [Drawer](/demos/drawers/) component.
* It uses [react-transition-group](https://github.com/reactjs/react-transition-group) internally.
*/
class Slide extends React.Component {
mounted = false;

constructor() {
super();

if (typeof window !== 'undefined') {
this.handleResize = debounce(() => {
// Skip configuration where the position is screen size invariant.
if (this.props.in || this.props.direction === 'down' || this.props.direction === 'right') {
return;
}

if (this.childDOMNode) {
setTranslateValue(this.props, this.childDOMNode);
}
}, 166); // Corresponds to 10 frames at 60 Hz.
}
}

componentDidMount() {
this.mounted = true;

// state.mounted handle SSR, once the component is mounted, we need
// to properly hide it.
if (!this.props.in) {
// We need to set initial translate values of transition element
// otherwise component will be shown when in=false.
this.updatePosition();
}
}

componentDidUpdate(prevProps) {
if (prevProps.direction !== this.props.direction && !this.props.in) {
// We need to update the position of the drawer when the direction change and
// when it's hidden.
this.updatePosition();
}
}

componentWillUnmount() {
this.handleResize.clear();
}
function Slide(props) {
const {
children,
direction,
in: inProp,
onEnter,
onEntering,
onExit,
onExited,
style,
theme,
timeout,
...other
} = props;

const childrenRef = React.useRef();
/**
* used in cloneElement(children, { ref: handleRef })
*/
const handleOwnRef = React.useCallback(ref => {
// #StrictMode ready
childrenRef.current = ReactDOM.findDOMNode(ref);
}, []);
const handleRef = useForkRef(children.ref, handleOwnRef);

handleEnter = node => {
setTranslateValue(this.props, node);
const handleEnter = () => {
const node = childrenRef.current;
setTranslateValue(direction, node);
reflow(node);

if (this.props.onEnter) {
this.props.onEnter(node);
if (onEnter) {
onEnter(node);
}
};

handleEntering = node => {
const { theme } = this.props;

const transitionProps = getTransitionProps(this.props, {
mode: 'enter',
});
const handleEntering = () => {
const node = childrenRef.current;
const transitionProps = getTransitionProps(
{ timeout, style },
{
mode: 'enter',
},
);
node.style.webkitTransition = theme.transitions.create('-webkit-transform', {
...transitionProps,
easing: theme.transitions.easing.easeOut,
Expand All @@ -141,17 +123,19 @@ class Slide extends React.Component {
});
node.style.webkitTransform = 'translate(0, 0)';
node.style.transform = 'translate(0, 0)';
if (this.props.onEntering) {
this.props.onEntering(node);
if (onEntering) {
onEntering(node);
}
};

handleExit = node => {
const { theme } = this.props;

const transitionProps = getTransitionProps(this.props, {
mode: 'exit',
});
const handleExit = () => {
const node = childrenRef.current;
const transitionProps = getTransitionProps(
{ timeout, style },
{
mode: 'exit',
},
);
node.style.webkitTransition = theme.transitions.create('-webkit-transform', {
...transitionProps,
easing: theme.transitions.easing.sharp,
Expand All @@ -160,78 +144,82 @@ class Slide extends React.Component {
...transitionProps,
easing: theme.transitions.easing.sharp,
});
setTranslateValue(this.props, node);
setTranslateValue(direction, node);

if (this.props.onExit) {
this.props.onExit(node);
if (onExit) {
onExit(node);
}
};

handleExited = node => {
const handleExited = () => {
const node = childrenRef.current;
// No need for transitions when the component is hidden
node.style.webkitTransition = '';
node.style.transition = '';

if (this.props.onExited) {
this.props.onExited(node);
if (onExited) {
onExited(node);
}
};

/**
* used in cloneElement(children, { ref: handleRef })
*/
handleRef = ref => {
// #StrictMode ready
this.childDOMNode = ReactDOM.findDOMNode(ref);
setRef(this.props.children.ref, ref);
};
const updatePosition = React.useCallback(() => {
if (childrenRef.current) {
setTranslateValue(direction, childrenRef.current);
}
}, [direction]);

React.useEffect(() => {
// Skip configuration where the position is screen size invariant.
if (!inProp && direction !== 'down' && direction !== 'right') {
const handleResize = debounce(() => {
if (childrenRef.current) {
setTranslateValue(direction, childrenRef.current);
}
}, 166); // Corresponds to 10 frames at 60 Hz.

updatePosition() {
if (this.childDOMNode) {
setTranslateValue(this.props, this.childDOMNode);
window.addEventListener('resize', handleResize);

return () => {
handleResize.clear();
window.removeEventListener('resize', handleResize);
};
}
}

render() {
const {
children,
direction,
in: inProp,
onEnter,
onEntering,
onExit,
onExited,
style,
theme,
...other
} = this.props;

return (
<EventListener target="window" onResize={this.handleResize}>
<Transition
onEnter={this.handleEnter}
onEntering={this.handleEntering}
onExit={this.handleExit}
onExited={this.handleExited}
appear
in={inProp}
{...other}
>
{(state, childProps) => {
return React.cloneElement(children, {
ref: this.handleRef,
style: {
visibility: state === 'exited' && !inProp ? 'hidden' : undefined,
...style,
...children.props.style,
},
...childProps,
});
}}
</Transition>
</EventListener>
);
}
return () => {};
}, [direction, inProp]);

React.useEffect(() => {
if (!inProp) {
// We need to update the position of the drawer when the direction change and
// when it's hidden.
updatePosition();
}
}, [inProp, updatePosition]);

return (
<Transition
onEnter={handleEnter}
onEntering={handleEntering}
onExit={handleExit}
onExited={handleExited}
appear
in={inProp}
timeout={timeout}
{...other}
>
{(state, childProps) => {
return React.cloneElement(children, {
ref: handleRef,
style: {
visibility: state === 'exited' && !inProp ? 'hidden' : undefined,
...style,
...children.props.style,
},
...childProps,
});
}}
</Transition>
);
}

Slide.propTypes = {
Expand Down
Loading

0 comments on commit 19d418d

Please sign in to comment.