React Js: How to Animate Conditionally Rendered Components

Smooth animation when turning off a conditionally rendered component in React

Looks like you need a simple Accordion thingy.
You could try something like that (snippet below).

One of the main moments here is setting the height to the auto value. It allows the content to change, and it won't strict its dimensions.

AccordionItem conditionally renders its children. If it should be closed and the animation is over, then no children will be rendered.

const AccordionItem = (props) => {
const { className, headline, open, children } = props

const [height, setHeight] = React.useState(0)
const [isOver, setOver] = React.useState(false)
const bodyRef = React.useRef(null)

const getDivHeight = React.useCallback(() => {
const { height } = bodyRef.current ? bodyRef.current.getBoundingClientRect() : {}

return height || 0
}, [])

// set `auto` to allow an inner content to change
const handleTransitionEnd = React.useCallback(
(e) => {
if (e.propertyName === 'height') {
setHeight(open ? 'auto' : 0)
if (!open) {
setOver(true)
}
}
},
[open]
)

React.useEffect(() => {
setHeight(getDivHeight())
setOver(false)

if (!open) {
requestAnimationFrame(() => {
requestAnimationFrame(() => setHeight(0))
})
}

}, [getDivHeight, open])

const shouldHide = !open && isOver

return (
<div style={{overflow: 'hidden'}}>
<div
style={{ height, transition: "all 2s" }}
onTransitionEnd={handleTransitionEnd}
>
<div ref={bodyRef}>
{shouldHide ? null : children}
</div>
</div>
</div>
)
}

const App = () => {
const [open, setOpen] = React.useState(false)

return (
<div>
<button onClick={() => setOpen(isOpen => !isOpen)}>toggle</button>

<table style={{width: '100%'}}>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
</table>
</div>
)
}

ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>

<div id="root"></div>

How to animate conditionally-rendered components?

yes - the answer found here effectively addresses the problem. To solve it, I moved the conditional logic of the component up, created appropriate variable, and encapsulated it inside a <Transition> in render(). If there is a lesson to be learned here, it is that <Transition> from Reakt Motion UI Pack (and, perhaps, elsewhere) does not fire its leave animation if surrounded by a conditional statement, making it impossible to use it together with ternary operator if you don't want the false component to be animated as well.

Conditional Rendering and ReactCSSTransitionGroup Animation

I've seen this same problem posted many times. In short: you need to conditionally render the children inside of <ReactCSSTransitionGroup>, not <ReactCSSTransitionGroup> itself. <ReactCSSTransitionGroup> needs to mount once and then stay, it's the children that get added and removed.

content.js

class Content extends Component {
render() {
const transitionOptions = {
transitionName: "fade",
transitionEnterTimeout: 500,
transitionLeaveTimeout: 500
}

let theChild = undefined;
if (this.props.page === 'one') {
theChild = <Comp1 key="comp1" />;
} else {
theChild = <Comp2 key="comp2" />;
}

return (
<div>
<ReactCSSTransitionGroup {...transitionOptions}>
{theChild}
</ReactCSSTransitionGroup>
</div>
);
}
}

Note that you should also add a unique key prop to each child inside of a <ReactCSSTransitionGroup>. That helps the component identify which children are unique in order to properly animate them in and out.

Issues on animating a conditionally rendered component using React-spring

What you were missing is this:

return expand.map(({ item, props, key }) => (
item && <animated.div
// ...etc

When you're controlling the mounting of a single component with useTransition, you need to conditionally render it based on the item being passed. In your case, when it's false it won't render (which will unmount if already mounted) and when it's true it will render (mount if unmounted).

Here's a working sandbox forked from yours: https://codesandbox.io/s/infallible-agnesi-cty5g.

A little more info

The first argument to useTransition is the list you want to transition. That watches for changes and sends back an array mapped with each item, a key and a style object (props) based on whether the item is truthy (entering) or falsy (leaving). So for a transition that mounts/unmounts a single element, conditionally rendering based on the truthiness of the item is key.

Check out the examples again here and you'll see the differences between transitioning a list, a toggle between two elements, and a single item.

For a list, no need to check for the existence of the item because the array changes.

For toggling between two elements, you use the truthiness of item to determine which element to render.

For a single element, item determines whether to render at all. This means it won't mount initially when you default to false, and will make sure you don't render 2 items whenever your isActive value changes.



Related Topics



Leave a reply



Submit