Animations as React components #2 - stacking elements
Tags: javascript, react, styledcomponents, css
Published: 2020-03-02 08:33:28

In my previous [post](https://dev.to/lkatkus/animations-as-react-components-595g), I suggested having animations as a separate component. Now I would like to share a bit more complex use case, which I encountered, when our design team wanted to "spice things up" - have a sidebar stack its elements one after another.

### Setup
So the goal is clear - render each element one after another and apply animation when "adding" the elements. To make this as simple and as reusable as possible I wanted to have a separate component which handles all of the involved logic. Nothing ground breaking here. This component should handle:
* Rendering the component one by one;
* Apply animation to the lastest "added" elements;
* Have a callback after all the elements have finished to handle some application state changes.

```javascript
<Composer
  shouldRender={state}
  transition={FadeIn}
  transitionDuration={300}
  onFinishCallback={enableButton}
>
  <Element>Element 1</Element>
  <Element>Element 2</Element>
  <Element>Element 3</Element>
  <Element>Element 4</Element>
  <Element>Element 5</Element>
</Composer>
```

### Composer
All of the requirements listed above can be easily achieved using only a few `useState` and `useEffect` hooks:
* To have a stacking effect, we will need to map `children` and compose `childrenToRender` using an iterator `tick`;
* While composing `childrenToRender` we will check if the `child` has to be animated `child index === tick`, or returned as it is `child index < tick` or not returned at all;
* After updating `childrenToRender` we will increment `tick` and repeat everything again until we gone throuh all the `children`;
* Last, but not least, after incrementing `tick` we check for `tick === children length` to see if all the elements here handled and call `onFinishCallback` if it is available.


```javascript
const Composer = ({
  children,
  shouldRender,
  transition: Transition,
  transitionDuration,
  onFinishCallback
}) => {
  /** Track which element should be animated */
  const [tick, setTick] = React.useState(-1);
  /** Stores children with animation to be rendered */
  const [childrenToRender, setChildrenToRender] = React.useState([]);

  /** Checks the passed props and starts iterating */
  React.useEffect(() => {
    if (shouldRender) {
      setTick(tick + 1);
    } else {
      setTick(-1);
      setChildrenToRender([]);
    }
  }, [shouldRender]);

  /** Iterates over children and adds animation if required */
  React.useEffect(() => {
    const updatedChildren = children.map((child, index) => {
      return index === tick ? (
        <Transition
          key={`animated-child-${index}`}
          duration={transitionDuration}
        >
          {child}
        </Transition>
      ) : index < tick ? (
        child
      ) : null;
    });

    /** Filters null children, to make prevent unnecessary iterations */
    setChildrenToRender(updatedChildren.filter(child => !!child));
  }, [tick]);

  /** Handles calling onFinishCallback */
  React.useEffect(() => {
    if (shouldRender && tick === children.length) {
      onFinishCallback && onFinishCallback();
    }
  }, [tick]);

  /** Checks if it is required to continue iterating over children  */
  React.useEffect(() => {
    if (shouldRender && tick < children.length) {
      setTimeout(() => {
        setTick(tick + 1);
      }, transitionDuration);
    }
  }, [childrenToRender]);

  return childrenToRender;
};
```

### Animation component
With the `Composer` set up, all that remains is the animation component.
[Here](https://dev.to/lkatkus/animations-as-react-components-595g) I suggested using `.attrs` to set `transition` and `transform` styles based on passed state. This setup is quite good, when you need to easily handle transitions in both directions and also be able to have intermediate transitions (e.g. when reverse animation is trigered before the finishing the initial).
In this case, it was not required so I have decided to go with `keyframes` as this removes the need to handle state changes for each element in the `Composer` and is a bit more straight forward.

```javascript
import styled, { keyframes } from "styled-components";

const getTransform = () => keyframes`
  from {
    transform: translateY(200px);
  }
  to {
    transform: translateY(0);
  }
`;

const getOpacity = () => keyframes`
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
`;

export default styled("div")`
  animation: ${getTransform()}, ${getOpacity()};
  animation-duration: ${({ duration }) => `${duration}ms`};
`;
```

### Results
Here are a few examples of the setup in action with a few different `transitions`.

{% codesandbox agitated-jones-34egp %}