Skip to main content

useAnimatePresence

useAnimatePresence(props: AnimatePresenceProps): AnimatePresenceReturn

useAnimatePresence

useAnimatePresence helps you animate components that are added or removed from the DOM.

Great for things like modals, tooltips, dropdowns, etc.

Demo

Usage

Basic example

By default all you need to provide is a boolean that determines whether the element should be visible or not. The hook will return a render boolean that you can use to conditionally render the element.

It will also return a animate boolean, which is a shorthand for state === OPENING || state === OPENED that you can use to conditionally apply your animation.

import { useState } from "react"
import { useAnimatePresence } from "@wethegit/react-hooks"

function MyComponent() {
const [isVisible, setIsVisible] = useState(false)

const { render, animate } = useAnimatePresence({
isVisible,
duration: 500,
})

return (
<>
<button onClick={() => setIsVisible((cur) => !cur)}>
{render ? "Hide" : "Show"}
</button>

{render && (
<div className={`my-component ${animate && "is-visible"}`}>
Hey! I animate in and out!
</div>
)}
</>
)
}

And then in your styles:

.my-component {
opacity: 0;
transition: opacity 500ms ease-out;

&.is-visible {
opacity: 1;
}
}

Custom animation

The hook will return a currentDuration number that you can use to set the duration of your animation. This is optional, but very useful so you don't have to hardcode the duration in your styles.

It will also return a state enum that you can use to control the animation in every step.

import { useAnimatePresence, AnimatePresenceStates } from "@local/hooks"

function MyComponent() {
// ... shortened for brevity
const { render, animate, currentDuration } = useAnimatePresence({
state,
isVisible,
duration: {
// different durations for enter and exit
enter: 1000,
exit: 500,
},
})

// you can control the animation at every step
let className
if (state === AnimatePresenceStates.ENTERING) {
className = "is-entering"
} else if (state === AnimatePresenceStates.ENTERED) {
className = "is-entered"
} else if (state === AnimatePresenceStates.EXITING) {
className = "is-exiting"
} else if (state === AnimatePresenceStates.EXITED) {
className = "is-exited"
}

return (
<>
{/* render it the same way, the hook takes care of updating the duration */}
{render && (
<div
className={`my-component ${className}`}
style={{
"--duration": `${currentDuration}ms`,
}}
>
Hey there!
</div>
)}
</>
)
}

And then in your styles:

.my-component {
transition: all var(--transition-duration) ease-out;

&.is-entering {
opacity: 1;
transform: scale(1.5);
}

&.is-entered {
opacity: 1;
transform: scale(1);
}

&.is-exiting,
&.is-exited {
opacity: 0;
transform: scale(0) rotate(360deg);
transition-timing-function: ease-in;
}
}

Accessibility

You can use the useUserPrefs hook to help you with accessibility by disabling animations when the user prefers reduced motion.

import { useAnimatePresence, useUserPrefs } from "@local/hooks"

function MyComponent() {
// use the prefersReducedMotion preference to disable animations
const { prefersReducedMotion } = useUserPrefs()
const { render, animate } = useAnimatePresence({
isVisible,
// disable animations if the user prefers reduced motion
duration: prefersReducedMotion ? 0 : 300,
})
}

Don't animate on mount

Set initial to true to have the element start out visible.

const { render, animate } = useAnimatePresence({
initial: true,
isVisible,
})

Parameters

NameType
propsAnimatePresenceProps

AnimatePresenceProps

duration

Optional duration: number | { enter: number ; exit: number }

Duration in miliseconds or object with enter and exit duration in miliseconds

Default Value

= 300

initial

Optional initial: boolean

Initial state of the animation, if true the component won't animate in on render

Default Value

false

isVisible

isVisible: boolean

Visibility of the component

Returns

AnimatePresenceReturn

render

render: boolean

render is an shorthand for animating the component in and out

currentDuration

currentDuration: number

Duration of the current animation

render

render: boolean

Render your component only if render is true

state

state: AnimatePresenceState

export enum AnimatePresenceState {
ENTERED = "entered",
EXITED = "exited",
EXITING = "exiting",
ENTERING = "entering",
MOUNTED = "mounted",
}

Current state of the animation. Use it for full control of the animation in all states