February 22, 2023
The Power of Keys in Framer Motion
Exploring how to use the React key prop to power your Framer Motion animations.
What do these animations have in common?
Well, they're both written in Framer Motion, but more importantly, they both take advantage of the React key
prop. That's right — the same key
prop that you use to suppress the React warning when writing loops:
["a", "b", "c"].map((item) => <div key={item}>{item}</div>);
["a", "b", "c"].map((item) => <div key={item}>{item}</div>);
You see, the key
prop is a lot more powerful than it seems, and in this post, we'll explore exactly what it is and how you can use it to create some pretty cool animations.
A Primer on Keys
The purpose of the key
prop is to uniquely identify a React component. If React sees that a component's key has changed between renders, it will unmount that component and mount a new one in its place.
For the purposes of this post, I'm using the term "React component" to refer to both custom components (like
const MyComponent = (props) => { ... }
) and JSX tags (like<div />
).
The implication of this is we can use keys to explicitly tell React to "re-mount" a component.
A Counter Component
Consider this little counter component:
const Counter = ({ name }) => {
const [count, setCount] = useState(0);
return (
<div>
<p>{name}</p>
<h1>{count}</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
Increment
</button>
</div>
);
};
const Counter = ({ name }) => {
const [count, setCount] = useState(0);
return (
<div>
<p>{name}</p>
<h1>{count}</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
Increment
</button>
</div>
);
};
Here, we render the component with the name "John". Try incrementing the counter a couple of times and then click on the "Change Name" button:
const [name, setName] = useState("John");
return (
<div>
<Counter name={name} />
<button
onClick={() => {
setName(name === "Jane" ? "John" : "Jane");
}}
>
Change Name
</button>
</div>
);
const [name, setName] = useState("John");
return (
<div>
<Counter name={name} />
<button
onClick={() => {
setName(name === "Jane" ? "John" : "Jane");
}}
>
Change Name
</button>
</div>
);
John
0
Notice that changing the name maintains the counter's value. Most of the time, this is what you want — imagine if the component's state resets every time one of its props changes!
What if you do want to reset the state, though? That's where keys come in. This time, we'll add a key prop to <Counter />
and set it to name
. Now try clicking on "Increment" then "Change Name":
const [name, setName] = useState("John");
return (
<div>
<Counter name={name} key={name} />
<button
onClick={() => {
setName(name === "Jane" ? "John" : "Jane");
}}
>
Change Name
</button>
</div>
);
const [name, setName] = useState("John");
return (
<div>
<Counter name={name} key={name} />
<button
onClick={() => {
setName(name === "Jane" ? "John" : "Jane");
}}
>
Change Name
</button>
</div>
);
John
0
This time, clicking on "Change Name" resets the counter to 0!
This state reset is just a byproduct, however; under the hood, React is unmounting the old instance of <Counter />
and mounting a new one in its place.
Short Quiz
Let's take a short quiz. Take a look at the following code and try to answer the question:
Now let's change the key of the component when we click on "Change Key":
But how is this useful to Framer Motion?
In Framer Motion, we can make mount animations using the animate
prop and unmount animations using AnimatePresence
. Since changing keys lets us re-mount components, we can essentially use keys to trigger animations!
Let's take a look at a few examples.
Refresh Component
One utility component I use all the time is this refresh component:
When the refresh button is clicked, the component remounts, causing the mount animation to play again. I've found this to be super handy when working on a component's mount animation!
Again, this works because we change the key of the <div />
element, thereby telling React to unmount the existing component and mount a new instance.
Animating Text Changes
Of course, this technique is useful outside of development as well.
In the Japanese app I'm working on, I have this button that changes its text content when the user submits an answer:
Using what we know about keys, how do you think we should implement this?
import React from 'react' import { motion } from 'framer-motion' export function NextButton() { const [toggled, toggle] = React.useReducer((state) => !state, false); return ( <motion.button layout id="motion" onClick={toggle}> <motion.span animate={{ opacity: 1 }} initial={{ opacity: 0 }} transition={{ delay: 0.2 }} > {toggled ? "Next" : "Show Answer"} </motion.span> </motion.button> ) }
By adding a key to the <motion.span />
element!
<motion.span key={toggled ? "done" : "ready"}>...</motion.span>
<motion.span key={toggled ? "done" : "ready"}>...</motion.span>
When we toggle the state, we simultaneously change the key of the <motion.span />
element, causing it to replay its mount animation.
Infinite Carousel
Here's another animation I'm especially fond of that uses keys in conjunction with AnimatePresence
:
中
What's really cool about this animation is how little code you need to implement it. Here it is in its entirety:
<div style={{ position: "relative", overflow: "hidden" }}>
<AnimatePresence mode="popLayout">
<motion.div
key={word}
initial={{ x: -300 }}
animate={{ x: 0 }}
exit={{ x: 300 }}
>
{word}
</motion.div>
</AnimatePresence>
</div>
<div style={{ position: "relative", overflow: "hidden" }}>
<AnimatePresence mode="popLayout">
<motion.div
key={word}
initial={{ x: -300 }}
animate={{ x: 0 }}
exit={{ x: 300 }}
>
{word}
</motion.div>
</AnimatePresence>
</div>
The key
in this case is serving a double purpose:
- First, it tells React to remount the component when the word changes;
- Second, it tells
AnimatePresence
that the child has changed, triggering the exit animation of the old word and the mount animation of the new word.
Another Solution
Now you don't technically need keys to implement this. If you know the number of items in your carousel ahead of time and you don't want your items to loop, you could technically line them all up and slide them across:
中
学
校
<motion.div
animate={{ x: -600 + currentIndex * 300 }}
style={{ display: "flex" }}
>
{KANJI.map((char) => (
<p key={char} style={{ width: 300, flexShrink: 0 }}>
{char}
</p>
))}
</Box>
<motion.div
animate={{ x: -600 + currentIndex * 300 }}
style={{ display: "flex" }}
>
{KANJI.map((char) => (
<p key={char} style={{ width: 300, flexShrink: 0 }}>
{char}
</p>
))}
</Box>
As with everything in computer science, there are multiple ways to solve a problem! Use the one that fits your use case the best.
Summary
To summarize, the key
prop is a special prop in React that lets you uniquely identify components. When a component's key changes, React will treat that component as a different component, unmounting the existing component.
In Framer Motion, we can exploit this behavior to trigger animations. For example, we can use keys to trigger mount animations by changing the key of the component we want to animate.
That's all for today; thanks for reading!