Skip to content

Instantly share code, notes, and snippets.

@dimensi
Last active March 12, 2019 15:07
Show Gist options
  • Save dimensi/448376ac9eb4b0ecba9989a4aca23cb5 to your computer and use it in GitHub Desktop.
Save dimensi/448376ac9eb4b0ecba9989a4aca23cb5 to your computer and use it in GitHub Desktop.
Switch transition for react-transition-group
import React from 'react'
import PropTypes from 'prop-types'
function areChildrenDifferent (oldChildren, newChildren) {
if (oldChildren === newChildren) return false
if (
React.isValidElement(oldChildren) &&
React.isValidElement(newChildren) &&
oldChildren.key != null &&
oldChildren.key === newChildren.key
) {
return false
}
return true
}
const ENTERED = 'ENTERED'
const ENTERING = 'ENTERING'
const EXITING = 'EXITING'
export class SwitchTransition extends React.Component {
static childContextTypes = {
transitionGroup: PropTypes.object.isRequired,
}
getChildContext () {
return {
transitionGroup: { isMounting: !this.appeared },
}
}
state = {
status: ENTERED,
current: null,
}
appeared = false
componentDidMount () {
this.appeared = true
}
static getDerivedStateFromProps (props, state) {
if (state.current && areChildrenDifferent(state.current, props.children)) {
return {
status: EXITING,
}
} else {
return {
current: React.cloneElement(props.children, {
in: true,
}),
}
}
}
changeState (status, current) {
this.setState({
status,
current,
})
}
render () {
const { status, current } = this.state
if (status === ENTERED) {
return current
}
if (status === ENTERING) {
return React.cloneElement(this.props.children, {
in: true,
onEntered: () => {
this.changeState(
ENTERED,
React.cloneElement(this.props.children, { in: true })
)
},
})
}
return React.cloneElement(current, {
in: false,
onExited: () => {
this.changeState(ENTERING, null)
},
})
}
}
import React from "react";
import PropTypes from "prop-types";
function areChildrenDifferent(oldChildren, newChildren) {
if (oldChildren === newChildren) return false;
if (
React.isValidElement(oldChildren) &&
React.isValidElement(newChildren) &&
oldChildren.key != null &&
oldChildren.key === newChildren.key
) {
return false;
}
return true;
}
const ENTERED = "ENTERED";
const ENTERING = "ENTERING";
const EXITING = "EXITING";
const modes = {
out: "out-in",
in: "in-out"
};
export class SwitchTransition extends React.Component {
static childContextTypes = {
transitionGroup: PropTypes.object.isRequired
};
getChildContext() {
return {
transitionGroup: { isMounting: !this.appeared }
};
}
state = {
status: ENTERED,
current: null
};
appeared = false;
componentDidMount() {
this.appeared = true;
}
static getDerivedStateFromProps(props, state) {
if (state.current && areChildrenDifferent(state.current, props.children)) {
return {
status: EXITING
};
} else {
return {
current: React.cloneElement(props.children, {
in: true
})
};
}
}
changeState(status, current = this.state.current) {
this.setState({
status,
current
});
}
renderOut() {
const {
props: { children },
state: { status, current }
} = this;
if (status === ENTERED) {
return current;
}
if (status === ENTERING) {
return React.cloneElement(children, {
in: true,
onEntered: () => {
this.changeState(ENTERED, React.cloneElement(children, { in: true }));
}
});
}
return React.cloneElement(current, {
in: false,
onExited: () => {
this.changeState(ENTERING, null);
}
});
}
renderIn() {
const {
props: { children },
state: { status, current }
} = this;
if (status === ENTERED) {
return current;
}
if (status === ENTERING) {
return [
React.cloneElement(current, {
in: false,
onExited: () => {
this.changeState(
ENTERED,
React.cloneElement(children, { in: true })
);
}
}),
React.cloneElement(children, {
in: true
})
];
}
return [
current,
React.cloneElement(children, {
in: true,
onEntered: () => {
this.changeState(ENTERING);
}
})
];
}
render() {
if (!this.props.mode || this.props.mode === modes.out) {
return this.renderOut();
}
return this.renderIn();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment