Written by David Omotayo✏️
Adding animation and transition effects to your website’s components and elements can improve user experience and also add some flair. However, transitions created with heavy animation libraries tend to be slow due to the overhead the library adds to the application.
While we can’t completely eliminate the performance dip created by animations even in smaller libraries, we can reduce it by selecting the right package.
In this tutorial, we'll demonstrate how to create fast transition animations in React using transition-hook.
This article will cover the following:
- What is transition-hook?
- How does transition-hook work?
- Using
useTransition
- Using
useSwitchTransition
- Using
listTransition
- Using an FaCC pattern
- FaCC pattern with
useTransition
- FaCC pattern with
useSwitchTransition
- FaCC pattern with
At the time of writing, transition-hook is not ready for production, so it can’t be used in actual applications. However, it has reached a v1.5.1 release status, so a stable, production-ready release may be just around the corner!
Getting started
To follow along with the examples used in this article, set up a React project and install transition-hook with Yarn or npm:
/*yarn*/
yarn add transition-hook
/*npm*/
npm install transition-hook --save
What is transition-hook?
transition-hook is one of many Hooks available for creating animations in React. It’s similar to the popular react-transition-group, but it’s lightweight, has simpler syntaxes, and is more performant.
To be clear, transition-hook is not an all-out animation library like Framer Motion or react-spring. It does not animate elements automatically. Instead, it allows you to add animations to an element’s entrance and exit from the DOM using its lifecycle methods with CSS classes and styles.
The overhead cost of React Hooks to add animations to your applications is negligible since Hooks are fairly small compared to full animation libraries.
For example, the minified version of the react-transition-group adds 13.5kB bundle weight to an application and it takes about 5ms to load up on a 4G network. For comparison, transition-hook adds only 4.3kB bundle weight and loads up in just 1ms. Its lightweight nature and its ability to render fast, optimized animations make transition-hook more performant than its predecessors.
Here's a side-by-side comparison of the bundle sizes of both libraries:
Source: bundlephobia
Source: bundlephobia
Here's a comparison showing how the transition-hook bundle size compares to other React animation libraries: react-spring, framer-motion, react-motion, and react-move:
Source: bundlephobia
How does transition-hook work?
transition-hook leverages the series of lifecycle methods that are invoked when a component enters and exits the DOM. transition-hook uses these lifecycle methods to create transition stages that allow us to add animations to components with CSS classes and styles based on when the components mount and unmount.
When a component is mounted, it gets inserted into the DOM as an element. The opposite happens when a component unmounts; it gets removed from the DOM. However, these lifecycle methods can be overridden to run desired logic at a particular time in the process.
transition-hook exposes three React Hooks:
-
useTransition
-
useSwitchTransition
-
listTransition
These Hooks transform a Boolean state into transition stages that can be invoked when a component mounts or unmounts after a specified duration. This allows us to add CSS styling based on different stages to create animation transitions.
Here are transition-hook's transition stages:
-
from
: before the element enters -
enter
: when the element enters -
leave
: before the element exits
Now, let’s look at some practical examples to get a clear idea of how this works.
Using useTransition
Below is an example of a simple component with fade-in and fade-out animations achieved using the useTransition
Hook:
const [onOff, setOnOff] = useState(true)
const {stage, shouldMount} = useTransition(onOff, 300) // (state, timeout)
return <div>
{shouldMount && (
<p style={{
….
transition: '.3s',
opacity: stage === 'enter' ? 1 : 0
}}>
I will fade
</p>
)}
<button onClick={()=>setOnOff(!onOff)}>toggle</button>
</div>
In this example, we first create an onOff
state variable with the useState
Hook, then pass it as a prop to the useTransition
Hook alongside a 3ms timeout duration.
The onOff
state variable indicates whether the component should enter the DOM by controlling the Boolean value of the destructured shouldMount
variable from the useTransition
Hook. If the value is set to true, the component will mount and the transition stages will be invoked for the duration of the specified timeout until the component unmounts.
const {stage, shouldMount} = useTransition(onOff, 300)
The stage
variable gives us access to the transition stages mentioned earlier. We can use the stage
variable to change the component’s CSS styling and animate the component.
But first, we need to check if the component has been mounted or not. We perform this check in the code snippet below using the logical AND (&&
) operator. The element to the right of the &&
would only be evaluated (mounted) if the Boolean value of the shouldMount
variable is true. In this case, the value of the onOff
state variable is set to true by default, so the component will be mounted, giving us access to the transition stages.
{shouldMount && (
<p style={{
….
transition: '.3s',
opacity: stage === 'enter' ? 1 : 0
}}>
I will fade
</p>
)}
In the below code, the CSS styles responsible for the animations are applied inline on the paragraph element. The opacity
property is where the magic happens!
Creating animations with CSS is as simple as adding a transition property to an element and toggling the opacity
or transform values conditionally. That's exactly what we've done in this example; the value of the opacity
property on the paragraph element is conditionally applied based on the transition
stages.
<p style={{
….
transition: 'all 3s',
opacity: stage === 'enter' ? 1 : 0
}}>
I will fade
</p>
When the component gets to the enter
transition stage, the paragraph’s opacity
value will be set to 1, otherwise, it will be set to zero.
opacity: stage === 'enter' ? 1 : 0
Since a transition
property is also applied to the element, a fade-in and fade-out effect will be created when the component mounts and unmounts, as shown below:
Animations can also be created by applying styles to elements with classes. But in this case, the className
is what will be applied conditionally:
<p className={stage === 'enter' ? 'fade-in' : 'fade-out'}>
I will fade
</p>
The useTransition
Hook is useful for creating simple and linear animations. However, when the animation is based on certain criteria and complex functionalities, the useSwitchTransition
and listTransition
Hooks are better options.
Next, we'll review several React Hooks that can be used to animate elements and look at specific use cases for each Hook.
Using useSwitchTransition
The useSwitchTransition
Hook animates elements based on state changes. Suppose we have a button that toggles back and forth between two states and requires a change in the appearance of another element. In this case, useSwitchTransition
would be the best Hook to use.
The useSwitchTransition hook accepts three props; state
, timeout
, and mode
. We've discussed the first two props in the previous sections, we'll take a look at the mode
prop and learn how to use it with a practical example.
The mode
prop indicates how the states of components change and how they enter or exit the scene. When the state of a component changes, the component exits and a new component with a new state enters.
The mode
prop accepts one of three values: default
, out-in
, or in-out
. The out-in
value indicates that the old state will exit the scene first before a new state enters. Conversely, the in-out
value indicates the new state will enter the scene before the old state exits.
The below example of an emoji switcher properly showcases a comprehensive use of the useSwitchTransition
Hook:
export function EmojiSwitchTransition() {
const [isHappy, setIsHappy] = useState(false)
const transition = useSwitchTransition(isHappy, 300)
return (
<div className="EmojiSwitchTransition">
<Button
onClick={() => {
setIsHappy(!isHappy)
}}
>
Toggle
</Button>
<div
style={{
display: 'flex',
justifyContent: 'center',
}}
>
{transition((state, stage) => (
<h1
style={{
transition: '.3s',
marginTop: 40,
fontSize: '5em',
position: 'absolute',
opacity: stage === 'enter' ? 1 : 0,
transformOrigin: 'center bottom',
transform: {
from: 'translateX(-100%) rotate(-90deg)',
enter: 'translateX(0%)',
leave: 'translateX(100%) rotate(90deg)',
}[stage],
}}
>
{state ? '🤣' : '😝'}
</h1>
))}
</div>
</div>
)
}
In this example, we have an isHappy
state variable and a transition
variable that stores the useSwitchTransition
function with the isHappy
state variable, a 3ms timeout, and a default
mode prop passed into it.
const [isHappy, setIsHappy] = useState(false)
const transition = useSwitchTransition(isHappy, 300, "default")
In the previous useTransition
example we destructured the stage and shouldMount
variables from the useTransition
Hook instead of storing them In a variable like this example.
In the component body, there’s a button with an event listener that toggles the isHappy
variable’s state between true and false, as well as a div element that wraps the transition render function.
{transition((state, stage) => (
<h1
style={{
transition: '.3s',
marginTop: 40,
fontSize: '5em',
position: 'absolute',
opacity: stage === 'enter' ? 1 : 0,
transformOrigin: 'center bottom',
transform: {
from: 'translateX(-100%) rotate(-90deg)',
enter: 'translateX(0%)',
leave: 'translateX(100%) rotate(90deg)',
}[stage],
}}
>
{state ? '🤣' : '😝'}
</h1>
))}
Inside the render function, there’s a callback function that has two parameters: state
and stage
. These parameters represent the isHappy
state and the transition
stages, respectively.
Inside the callback function is an h1
element that displays one of two emojis based on the current state.
{state ? '🤣' : '😝'}
These emojis are animated by the inline styles applied to the h1
element. The opacity
and transform
properties are responsible for the animations and they are set based on the transition stages.
style={{
...
opacity: stage === 'enter' ? 1 : 0,
...
}}
The opacity
of each emoji is set to 0 by default, when the state changes and it gets to the enter stage, the opacity
will be set to 1, and 0 on leave. This adds a fade-in and fade-out animation to the element when it enters and exits the scene.
The transform
method adds a translate and rotate animation to the element based on the transition stages.
style={{
...
…
transform: {
from: 'translateX(-100%) rotate(-90deg)',
enter: 'translateX(0%)',
leave: 'translateX(100%) rotate(90deg)',
}[stage],
}}
When in the from
transition stage, the emoji moves from the negative plane ( i.e from the left ) of the x-axis and is rotated 90 degrees counterclockwise as it enters the scene.
In the enter
transition stage, the translate method is set to zero, thus making the emoji adopt its default transform position. The leave transition stage initiates the exit animation by moving the emoji from the default position to the positive plane of the x-axis ( i.e to the right ) and rotating it 90 degrees clockwise.
Using listTransition
The listTransition
Hook is best if you have a list of items and you want to animate whenever an element is added or removed from the list.
This Hook accepts a list of arrays and useRef
Hook’s current property as a timeout prop. In the component body, it encapsulates a callback function with two parameters: item
and stage
. The item
parameter represents the array of items that will be animated with the stage
parameter based on the transition stages.
Here's a practical use of the listTransition
Hook from CodeSandbox:
export function ListShifting() {
const [list, setList] = useState([1])
const timeoutRef = useRef(300)
const transition = useListTransition(list, timeoutRef.current)
return (
<div className="ListShifting">
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 20,
marginBottom: 50,
}}
>
<Button onClick={() => setList((prev) => prev.concat(prev.length + 1))}>
Add Item
</Button>
<Button
variant="danger"
onClick={() => {
setList([])
timeoutRef.current = list.length * 50
}}
>
Remove All
</Button>
</div>
{transition((item, stage) => (
<h1
style={{
transition: '.3s',
...(stage === 'leave' && { transitionDelay: item * 50 + 'ms' }),
opacity: stage === 'enter' ? 1 : 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 20,
transformOrigin: 'center right',
transform: {
from: 'translateX(-100%) rotate(90deg)',
enter: 'translateX(0%)',
leave: 'translateX(100%) rotate(-90deg)',
}[stage],
}}
>
Item: {item}
<Button
variant="danger"
size="sm"
onClick={() =>
setList((prev) => prev.filter((pitem) => pitem !== item))
}
>
<RiSubtractLine />
</Button>
</h1>
))}
</div>
)
}
Using an FaCC pattern
transition-hook provides the option of creating animations using the Function as Child Component (FaCC) pattern. This pattern lets you pass a render function to a component as the child prop.
This is a more concise and comprehensive way of creating animations with transition-hook. We can use this pattern to define transition functions as components and pass the state
, timeout
, and mode
to them as props.
FaCC pattern with useTransition
Here’s an example of a Transition
function in JSX:
const [onOff, setOnOff] = useState(true);
const {stage, shouldMount} = useTransition(onOff, 300);
...
{shouldMount && (
{...}
)}
Here’s the same example using an FaCC pattern with useTransition
:
const [onOff, setOnOff] = useState(true);
...
<Transition state={onOff} timeout={300}>
{...}
</Transition>
FaCC pattern with useSwitchTransition
Here’s an example of a SwitchTransition
function in JSX:
const [isHappy, setIsHappy] = useState(false);
const transition = useSwitchTransition(isHappy, 300, "default");
...
{transition((state, stage) => (
{...}
)}
Here’s the same example using an FaCC pattern with useSwitchTansition
:
const [isHappy, setIsHappy] = useState(false);
...
<SwitchTransition state={isHappy} timeout={300} mode='default'>
{...}
</SwitchTransition>
FaCC pattern with listTransition
Here’s an example of a listTransition
function in JSX:
const [list, setList] = useState([1]);
const timeoutRef = useRef(300);
const transition = useListTransition(list, timeoutRef.current);
...
{transition((item, stage) => (
{...}
)
}
Here’s the same example using an FaCC pattern with listTransition
:
const [list, setList] = useState([1]);
const timeoutRef = useRef(300);
...
<listTransition state={list} timeout={timeoutRef.current}>
{...}
</listTransition>
Conclusion
In this tutorial, we discussed the features of transition-hook and how it works. We also demonstrated how to use transition-hook to animate React components on their entrance or exit from the DOM, and we looked at specific use cases for each Hook exposed by the library.
To learn more about transition-hook, visit GitHub. For additional examples and use cases, visit this CodeSandbox.
Full visibility into production React apps
Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.
Top comments (0)