DEV Community

Cover image for Crafting Silky Smooth Transitions in React 🌊
sanderdebr
sanderdebr

Posted on

Crafting Silky Smooth Transitions in React 🌊

Noticed how some React apps have very smooth transitioning between their pages or views? These animations can improve the user experience tremendously.

In this tutorial I will show you how do this by using the React Transition Group, which used to be integrated in React but now has been moved to the react-transition-group package. It's is a very popular package for managing component states (including mounting and unmounting) over time, especially with animation in mind.

Combined with styled components and CSS animations we will make some awesome transitions! 😄


1. Setup

Let's start with creating a basic app that shows the user alerts on click of a button:

The notifications come and go, but it does not look so great without some effect. We can create an animation with CSS or JavaScript to e.g. fade them in and out but we want full control of our component state. What if you have a component that is loading asynchronous in the background and you're not sure when it has mounted? Let's continue.

Our App component holds the state of notifications with the useState hook. As default we set an empty array. By using styled-components we can add a globalStyle for our app. With the addNotification() function the button is able to add an notification object to the array. Then we map over the notifications to show them.

Note that inside our notification component, the background color is decided based on the props of the notification with styled components. As well as the margin top which is based on the amount of notifications there are, awesome! 👾

notification

Next up we will make sure the notifications disappear after some time. We will handle this side effect inside the useEffect() hook. In here we'll set a time out function that slices the last element of our notifications array. We'll set the dependency to our notifications array so that it only fires when that changes.

Make sure to remove the timeout when the component unmounts, you can do this by adding a function on return of the useEffect() hook.

notification2


2. Transitioning

Let's improve the entering and exiting of our notifications with the react-transition-group package. First add the package with npm or yarn.

By wrapping out notification inside the transition component, we can modify it's properties based on the state of the transition:

  • entering
  • entered
  • exiting
  • exited

To add the transitions we need to wrap our notification component inside the transition component and give it an in property to decide the state and a timeout property to decide the length of the animation.

import { Transition } from "react-transition-group";

const MyNotification = ({ msg, visible, ...otherProps }) => {
  return (
    <Transition in={visible} timeout={3000}>
      {status => {
        return (
          <Notification status={status} {...otherProps}>
            {msg}
          </Notification>
        );
      }}
    </Transition>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now we can activate CSS animations based on the visibility status of the component. We also have to change the margin-top props for our notification component, as we need to count the amount of visible notifications instead of all notifications. We'll keep track of this in the useEffect hook which updated every time our notifications array gets updated.

  const [visibleNotifications, setVisibleNotifications] = useState([]);

  useEffect(() => {
    // Amount of visible notifications
    const visibleNotifications = notifications.filter(
      notification => notification.visible === true
    ).length;
    setVisibleNotifications(visibleNotifications);
  }, [notifications]);
Enter fullscreen mode Exit fullscreen mode
const fadeIn = () => keyframes`
  0% { opacity: 0; }
  100% { opacity: 1 }
`;

const fadeOut = () => keyframes`
  0% { opacity: 1; }
  100% { opacity: 0 }
`;

const Notification = styled.div`
  position: absolute;
  border-radius: 7px;
  background: white;
  padding: 1rem;
  right: 1rem;
  top: ${props => `${props.visibleNotifications * 4 + 1}rem`};
  background-color: ${props =>
    props.type === "success" ? "#48bb78" : "#c53030"};
  opacity: 0;

  ${props =>
    props.status === "entered" &&
    css`
      animation: 1s linear forwards ${fadeIn};
    `}

  ${props =>
    props.status === "exiting" &&
    css`
      animation: 1s linear forwards ${fadeOut};
    `}
`;
Enter fullscreen mode Exit fullscreen mode

Notice how we are changing the CSS animation based of the props status 👏

We then have to move our setTimeOut function outside of the useEffect() hook, inside the addNotification() function to avoid firing off every time our notifications state object gets changed. Also we'll give our notification the ID property so that we can refer back to it when changing the visibility.

Then let's update the notification array by changing the visible property to false after 3 seconds. We do not want to modify the state array directly because of immutability.

To do this, we will make a copy of the previous state which we receive from the useState() hook, update our notification by finding the right ID, and putting it back into our copied state. Then we'll update the state with our newly created state!

// Setting the visibility to 0 after x seconds
    setTimeout(() => {
      setNotifications(prevState => {
        // Copying the previous state
        let newState = [...prevState];
        // Updating our notification
        const target = { ...prevState[notifications.length], visible: false };
        // Putting updated notification back in copied state array
        newState[notifications.length] = target;
        // Updating our state with our new state!
        return newState;
      });
    }, 3000);
Enter fullscreen mode Exit fullscreen mode

Make sure to set the timeOut duration the same as the duration of the transition timOut inside the notification component.

That's it, our notifications are coming in way smoother, awesome!

Checkout the full code:

Next steps could be:

  • Setting ID with uuid() package instead
  • Removing notifications from the array instead of setting the visiblity to 0
  • Crazier CSS animations

Make sure to follow me for more tricks! 🧠

Top comments (0)