Skip to content

Instantly share code, notes, and snippets.

@koba04
Created April 5, 2022 15:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koba04/5575ded08b7ece5ca5326c797949d653 to your computer and use it in GitHub Desktop.
Save koba04/5575ded08b7ece5ca5326c797949d653 to your computer and use it in GitHub Desktop.
import PropTypes from 'prop-types';
import React from 'react';
import type { ReactElement } from 'react';
import ReactDOM from 'react-dom';
import TransitionGroup from './TransitionGroup';
import type { Props as TransitionProps } from './Transition';
type Props = Omit<TransitionProps, 'children'> & {
children: [ReactElement<TransitionProps>, ReactElement<TransitionProps>];
};
type HandlerNames =
| 'onEnter'
| 'onEntering'
| 'onEntered'
| 'onExit'
| 'onExiting'
| 'onExited';
type Args = [HTMLElement | boolean, boolean | undefined];
type ChildElement = ReactElement<TransitionProps>;
type ReplaceElements = [ChildElement, ChildElement];
/**
* The `<ReplaceTransition>` component is a specialized `Transition` component
* that animates between two children.
*
* ```jsx
* <ReplaceTransition in>
* <Fade><div>I appear first</div></Fade>
* <Fade><div>I replace the above</div></Fade>
* </ReplaceTransition>
* ```
*/
class ReplaceTransition extends React.Component<Props> {
handleEnter = (...args: Args) => this.handleLifecycle('onEnter', 0, args);
handleEntering = (...args: Args) =>
this.handleLifecycle('onEntering', 0, args);
handleEntered = (...args: Args) => this.handleLifecycle('onEntered', 0, args);
handleExit = (...args: Args) => this.handleLifecycle('onExit', 1, args);
handleExiting = (...args: Args) => this.handleLifecycle('onExiting', 1, args);
handleExited = (...args: Args) => this.handleLifecycle('onExited', 1, args);
handleLifecycle(handler: HandlerNames, idx: number, originalArgs: Args) {
const { children } = this.props;
// @ts-expect-error FIXME: Type 'string' is not assignable to type 'ReactElement<Props, string | JSXElementConstructor<any>>'.ts(2322)
const child: ChildElement = React.Children.toArray(children)[idx];
// @ts-expect-error FIXME: Type 'false' is not assignable to type '(((boolean | HTMLElement) & (HTMLElement | undefined)) & (HTMLElement | undefined)) & (HTMLElement | undefined)'.ts(2345)
if (child.props[handler]) child.props[handler](...originalArgs);
if (this.props[handler]) {
const maybeNode = child.props.nodeRef
? undefined
: ReactDOM.findDOMNode(this);
// @ts-expect-error FIXME: Argument of type 'Element | Text | null | undefined' is not assignable to parameter of type 'HTMLElement'.ts(2769)
this.props[handler](maybeNode);
}
}
render() {
const { children, in: inProp, ...props } = this.props;
// @ts-expect-error FIXME: Target requires 2 element(s) but source may have fewer.ts(2322)
const [first, second]: ReplaceElements = React.Children.toArray(children);
// @ts-expect-error The operand of a 'delete' operator must be optional.ts(2790)
delete props.onEnter;
// @ts-expect-error
delete props.onEntering;
// @ts-expect-error
delete props.onEntered;
// @ts-expect-error
delete props.onExit;
// @ts-expect-error
delete props.onExiting;
// @ts-expect-error
delete props.onExited;
return (
<TransitionGroup {...props}>
{inProp
? React.cloneElement(first, {
key: 'first',
onEnter: this.handleEnter,
onEntering: this.handleEntering,
onEntered: this.handleEntered,
})
: React.cloneElement(second, {
key: 'second',
onEnter: this.handleExit,
onEntering: this.handleExiting,
onEntered: this.handleExited,
})}
</TransitionGroup>
);
}
}
// @ts-expect-error To make TS migration diffs minimum, I've left propTypes here instead of defining a static property
ReplaceTransition.propTypes = {
in: PropTypes.bool.isRequired,
children(props: any, propName: HandlerNames) {
if (React.Children.count(props[propName]) !== 2)
return new Error(
`"${propName}" must be exactly two transition components.`
);
return null;
},
};
export default ReplaceTransition;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment