src/TransitionGroup.js (129 lines of code) (raw):
import PropTypes from 'prop-types';
import React from 'react';
import TransitionGroupContext from './TransitionGroupContext';
import {
getChildMapping,
getInitialChildMapping,
getNextChildMapping,
} from './utils/ChildMapping';
const values = Object.values || ((obj) => Object.keys(obj).map((k) => obj[k]));
const defaultProps = {
component: 'div',
childFactory: (child) => child,
};
/**
* The `<TransitionGroup>` component manages a set of transition components
* (`<Transition>` and `<CSSTransition>`) in a list. Like with the transition
* components, `<TransitionGroup>` is a state machine for managing the mounting
* and unmounting of components over time.
*
* Consider the example below. As items are removed or added to the TodoList the
* `in` prop is toggled automatically by the `<TransitionGroup>`.
*
* Note that `<TransitionGroup>` does not define any animation behavior!
* Exactly _how_ a list item animates is up to the individual transition
* component. This means you can mix and match animations across different list
* items.
*/
class TransitionGroup extends React.Component {
constructor(props, context) {
super(props, context);
const handleExited = this.handleExited.bind(this);
// Initial children should all be entering, dependent on appear
this.state = {
contextValue: { isMounting: true },
handleExited,
firstRender: true,
};
}
componentDidMount() {
this.mounted = true;
this.setState({
contextValue: { isMounting: false },
});
}
componentWillUnmount() {
this.mounted = false;
}
static getDerivedStateFromProps(
nextProps,
{ children: prevChildMapping, handleExited, firstRender }
) {
return {
children: firstRender
? getInitialChildMapping(nextProps, handleExited)
: getNextChildMapping(nextProps, prevChildMapping, handleExited),
firstRender: false,
};
}
// node is `undefined` when user provided `nodeRef` prop
handleExited(child, node) {
let currentChildMapping = getChildMapping(this.props.children);
if (child.key in currentChildMapping) return;
if (child.props.onExited) {
child.props.onExited(node);
}
if (this.mounted) {
this.setState((state) => {
let children = { ...state.children };
delete children[child.key];
return { children };
});
}
}
render() {
const { component: Component, childFactory, ...props } = this.props;
const { contextValue } = this.state;
const children = values(this.state.children).map(childFactory);
delete props.appear;
delete props.enter;
delete props.exit;
if (Component === null) {
return (
<TransitionGroupContext.Provider value={contextValue}>
{children}
</TransitionGroupContext.Provider>
);
}
return (
<TransitionGroupContext.Provider value={contextValue}>
<Component {...props}>{children}</Component>
</TransitionGroupContext.Provider>
);
}
}
TransitionGroup.propTypes = {
/**
* `<TransitionGroup>` renders a `<div>` by default. You can change this
* behavior by providing a `component` prop.
* If you use React v16+ and would like to avoid a wrapping `<div>` element
* you can pass in `component={null}`. This is useful if the wrapping div
* borks your css styles.
*/
component: PropTypes.any,
/**
* A set of `<Transition>` components, that are toggled `in` and out as they
* leave. the `<TransitionGroup>` will inject specific transition props, so
* remember to spread them through if you are wrapping the `<Transition>` as
* with our `<Fade>` example.
*
* While this component is meant for multiple `Transition` or `CSSTransition`
* children, sometimes you may want to have a single transition child with
* content that you want to be transitioned out and in when you change it
* (e.g. routes, images etc.) In that case you can change the `key` prop of
* the transition child as you change its content, this will cause
* `TransitionGroup` to transition the child out and back in.
*/
children: PropTypes.node,
/**
* A convenience prop that enables or disables appear animations
* for all children. Note that specifying this will override any defaults set
* on individual children Transitions.
*/
appear: PropTypes.bool,
/**
* A convenience prop that enables or disables enter animations
* for all children. Note that specifying this will override any defaults set
* on individual children Transitions.
*/
enter: PropTypes.bool,
/**
* A convenience prop that enables or disables exit animations
* for all children. Note that specifying this will override any defaults set
* on individual children Transitions.
*/
exit: PropTypes.bool,
/**
* You may need to apply reactive updates to a child as it is exiting.
* This is generally done by using `cloneElement` however in the case of an exiting
* child the element has already been removed and not accessible to the consumer.
*
* If you do need to update a child as it leaves you can provide a `childFactory`
* to wrap every child, even the ones that are leaving.
*
* @type Function(child: ReactElement) -> ReactElement
*/
childFactory: PropTypes.func,
};
TransitionGroup.defaultProps = defaultProps;
export default TransitionGroup;