Picture of Chris Eagle

Chris EagleWriting about web development and other things.

Leveraging React Keys to handle animation triggers

·5 min read

If you've been working with React for a while, you will be very familiar with the missing "key" prop error which will always scream at you when you forget to the add the aforementioned key prop to elements being rendered using an iterator like Array.map() or similar. However did you know that keys can also be super useful for solving a super irritating problem — triggering CSS animations when props change.

React key prop superpowers

The key prop is used by React to aid in determining what has changed when dynamic data is updated, sorted or filtered. Typically the data you're working with will or should include a unique identifier that is suitable to use for the key={item.uuid} prop when rendering in this manner, however you can also use the key prop outside of lists to tell React when dynamic data to a component has changed. When a key is used in this way and the key value changes, React will destroy the DOM nodes that are attached and recreate them. This provides us with a super useful API for forcing a render and resetting local state without worrying about keeping track in a local state variable in the child component itself.

Example

In the below example we have a Panel() functional component which accepts 2 props; title and content. This component will update based on user interaction with a parent component, think of this as a landscape oriented accordian, where the left has a vertical list of subjects, and the panel displays the related content. You can find the CodeSandbox embedded below the example code, if you want to fiddle. We want this component to have a (ugly) bounce in animation applied to the title when the users clicks between the subjects.

// app.js

function Panel({ title, content }) {
  return (
    <div className={`panel`}>
      <h2 className={`animate`}>{title}</h2>
      <p>{content}</p>
    </div>
  );
}


export default function App() {
  const [activePane, setActivePane] = useState(DATA[0].id);
  const panelData = DATA.filter((item) => item.id === activePane)[0];

  return (
    <div className="App">
      <ul>
        {DATA.map(({ id, title }) => (
          <li key={id}>
            <button onClick={() => setActivePane(id)}>{title}</button>
          </li>
        ))}
      </ul>
      {panelData && (
        <Panel
          key={panelData.id}
          title={panelData.title}
          content={panelData.content}
        />
      )}
    </div>
  );
}

Now when you look at the above example, you can see we have the animate class on the h2 element. This is the class that is responsible for triggering the animation. I've seen this situation handled in code bases of all sizes with useEffect() and setTimeOut() pulled in, and probably some additional useState() as well, all being used to update the class name directly when props change. This results in a heck of alot of boilerplate in the component just to re-animate when the props change.

Let's see if you can spot on the difference below which uses the power of keys to achieve the effect we want.

// app.js

function Panel({ title, content }) {
  return (
    <div className={`panel`}>
      <h2 className={`animate`}>{title}</h2>
      <p>{content}</p>
    </div>
  );
}


export default function App() {
  const [activePane, setActivePane] = useState(DATA[0].id);
  const panelData = DATA.filter((item) => item.id === activePane)[0];

  return (
    <div className="App">
      <ul>
        {DATA.map(({ id, title }) => (
          <li key={id}>
            <button onClick={() => setActivePane(id)}>{title}</button>
          </li>
        ))}
      </ul>
      {panelData && (
        <Panel
          key={panelData.id}
          title={panelData.title}
          content={panelData.content}
        />
      )}
    </div>
  );
}

Just from the single inclusion of key and the unique id value passed to it, we are telling React to destroy this component when the key changes, as that component no longer exists. This will cause React to recreate the underlying structure which in turns causes the CSS to be triggered in the browser, meaning our ugly animation is reapplied automagically. Lovely!

Check out the below CodeSandbox for a live demo.

The following link from the updated React docs explain this is far better detail than I ever could, and are really worth a read.

You might not need an effect

Cheerio!

Chris