Animations as React components
Tags: react, javascript, styledcomponents, css
Published: 2020-02-17 07:14:49

Story begins as usual - project is just starting, the design is "almost" done and the requirements are all over the place. Not wanting to deal with with major refactorings later down the road, team decides to follow [atomic design pattern](https://medium.com/@janelle.wg/atomic-design-pattern-how-to-structure-your-react-application-2bb4d9ca5f97) as much as possible.
Life is good. All changes are isolated in small chunks, but suddenly, a wild *animation* for an already developed component appears! [styled-components](https://styled-components.com/) to the rescue!

### Animation component
As an example, let's create a simple animation for rotating an item. It is just a simple wrapper, which uses chainable `.attrs` to pass dynamic props and set `animation` properties. Note: it should only use `css` and values, that can be used in transitions. So no `px` to `%` transitions.
For passing props, you could also use [tagged template literal](https://styled-components.com/docs/advanced#tagged-template-literals), but it would create a new classname for each different variant of the transition.

```javascript
import styled from "styled-components";

const Rotate = styled("div").attrs(
  ({ state, duration = "300ms", start = 0, end = 180 }) => ({
    style: {
      transition: duration,
      transform: `rotate(${state ? start : end}deg)`
    }
  })
)``;

export default Rotate;

```

### Usage
To use it, just import the animation, wrap the component you want to animate and provide some sort of a state handler. In this case it is just a simple component to change the state, when clicking a button. In practice, it could be almost anything from a button click, to a form validation status.

```javascript
<StateSwitcher>
  {({ state }) => (
    <Rotate state={state} duration="1s" end={360}>
      <Element>Rotate</Element>
    </Rotate>
  )}
</StateSwitcher>

```

### Combining multiple animations
Rinse and repeat. The setup is almost identical.
```javascript
import styled from "styled-components";

const Opacity = styled("div").attrs(
  ({ state, duration = "300ms", start = 0, end = 1 }) => ({
    style: {
      transition: duration,
      opacity: state ? end : start
    }
  })
)``;

export default Opacity;
```

Now use it to wrap and *voila*.

```javascript
<StateSwitcher>
  {({ state }) => (
    <Opacity state={state}>
      <Rotate state={state}>
        <Element>Rotate + Opacity</Element>
      </Rotate>
    </Opacity>
  )}
</StateSwitcher>
```

### Testing
Testing this setup is dead simple with [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro). Just change the state and check what the resulting style changes.

```javascript
import React from "react";
import { render } from "@testing-library/react";

import Rotate from "./Rotate";

describe("Rotate", () => {
  it("renders Rotate and changes state ", async () => {
    const component = state => (
      <Rotate state={state} start={0} end={123} data-testid="rotate-transition">
        <div>COMPONENT</div>
      </Rotate>
    );

    const { rerender, getByTestId } = render(component(true));
    const RenderedComponent = getByTestId("rotate-transition");
    let style = window.getComputedStyle(RenderedComponent);

    expect(style.transform).toBe("rotate(0deg)");
    rerender(component(false));

    style = window.getComputedStyle(RenderedComponent);

    expect(style.transform).toBe("rotate(123deg)");
  });
});
```

### Results
You could have many different variants (move, rotate, color ...) and extend these much more - handle animation finish callbacks, setTimeouts and etc.

This setup might not be the suitable in all cases, but in my case, it ticks all the right marks:
* Easy to use and share;
* Easy to extend;
* Easy to test;

{% codesandbox serene-hill-n5k2v %}