Draggin’ and Droppin’ in React

The React ecosystem offers us a lot of libraries that all are focused on the interaction of drag and drop. We have react-dnd, react-beautiful-dnd, react-drag-n-drop and many more, but some of them require quite a lot of work to build even a simple drag and drop demo, and some do not provide you with more complex functionality (e.g. multiple drag and drop instances), and if they do, it becomes very complex.

This is where react-sortable-hoc comes into play.

💡 This tutorial requires basic knowledge of React library and React hooks.

This library has “HOC" in its name for a good reason. It provides higher-order components that extends a component with drag and drop functionality.

Let’s walk through an implementation of its functionalities.

Spinning up a project

For this tutorial we are going to build an app with funny GIFs (from Chris Gannon!) that can be dragged around the viewport.

GitHub Repo

Let's create a simple app and add drag-n-drop functionality to it. We're going to use create-react-app to spin up a new React project:

npx create-react-app your-project-name

Now let's change to the project directory and install react-sorting-hoc and array-move. The latter is needed to move items in an array to different positions.

cd your-project-name
yarn add react-sortable-hoc array-move

Adding styles, data and GIF component

For simplicity's sake, we are going to write all styles in our App.css file. You can overwrite styles you have there with the following ones:

.App {
  background: #1a1919;
  color: #fff;
  min-height: 100vh;
  padding: 25px;
  text-align: center;
}

.App h1 {
  font-size: 52px;
  margin: 0;
}

.App h2 {
  color: #f6c945;
  text-transform: uppercase;
}

.App img {
  cursor: grab;
  height: 180px;
  width: 240px;
}

Let's create our state with GIFs. For this purpose we gonna use React’s built-in useState hook:

import React, { useState } from 'react';

Now add following before the return statement:

const [gifs, setGifs] = useState([
  'https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif',
  'https://media.giphy.com/media/l46CbZ7KWEhN1oci4/giphy.gif',
  'https://media.giphy.com/media/3ohzgD1wRxpvpkDCSI/giphy.gif',
  'https://media.giphy.com/media/xT1XGYy9NPhWRPp4pq/giphy.gif',
]);

It's time to create our simple GIF component. Create a Gif.js file in the src directory and pass in the following code:

import React from 'react';
import PropTypes from 'prop-types';

const Gif = ({ gif }) => (<img src={gif} alt="gif" />)

Gif.propTypes = {
  gif: PropTypes.string.isRequired,
};

export default Gif;

We always try to follow the best practices while writing code; thus we also import PropTypes for type checking.

Import the Gif component and add it to the main App component. With a bit of clean up, it looks like this:

import React, { useState } from 'react';
import './App.css';

import Gif from './Gif';

const App = () => {
  const [gifs, setGifs] = useState([
    'https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif',
    'https://media.giphy.com/media/l46CbZ7KWEhN1oci4/giphy.gif',
    'https://media.giphy.com/media/3ohzgD1wRxpvpkDCSI/giphy.gif',
    'https://media.giphy.com/media/xT1XGYy9NPhWRPp4pq/giphy.gif',
  ]);


  return (
    <div className="App">
      <h1>Drag those GIFs around</h1>
      <h2>Set 1</h2>
        {gifs.map((gif,  i) => <Gif key={gif} gif={gif} />)}
    </div>
  );
}

export default App;

Go to http://localhost:3000/ to see what the app looks like now:

A screenshot of react-sortable-hoc-article-app

Onto the drag-n-drop stuff

Alright, it's time make our GIFs draggable! And droppable.

To start, we need two HOCs from react-sortable-hoc, and the arrayMove method from the array-move library to modify our new array after dragging happens. We want our GIFs to stay on their new positions, right? Well, that’s what this is going to allow us to do.

Let's import them:

import { sortableContainer, sortableElement } from 'react-sortable-hoc';
import arrayMove from 'array-move';

As you might have guessed, those components will be wrappers which will expose functionality needed for us.

  • sortableContainer is a container for our sortable elements.
  • sortableElement is a container for each single element we are rendering.

Let's do the following after all our imports:

const SortableGifsContainer = sortableContainer(({ children }) => <div className="gifs">{children}</div>);
    
const SortableGif = sortableElement(({ gif }) => <Gif key={gif} gif={gif} />);

We've just created a container for our children elements that would be passed inside our SortableGifsContainer and also created wrapper for a single Gif component.
If it's a bit unclear to you, no worries — you will understand right after we implement it.

💡Note: You need to wrap your children in a div or any other valid HTML element.

It's time to wrap our GIFs into the SortableGifsContainer and replace the Gif component with our newly created SortableGif:

<SortableGifsContainer axis="x" onSortEnd={onSortEnd}>
  {gifs.map((gif, i) =>
    <SortableGif
    // don't forget to pass index prop with item index
      index={i}
      key={gif}
      gif={gif}
    />
  )}
</SortableGifsContainer>

It’s important to note that you need to pass the index prop to your sortable element so the library can differentiate items. It's similar to adding keys to the lists in React).

We add axis because our items are positioned horizontally and we want to drag them horizontally, while default is vertical dragging. In other words, we’re limiting dragging along the horizontal x-axis. As you can see we also add an onSortEnd function, which triggers every time we drag or sort our items around. There are, of course, a lot more events but you can find more info in the documentation which already does an excellent job of covering them.

Time to implement it! Add the following line above the return statement:

const onSortEnd = ({ oldIndex, newIndex }) => setGifs(arrayMove(gifs, oldIndex, newIndex));

I want to explain one more thing: our function received an old and new index of the item which was dragged and, of course, each time after we move items around we modify our initial array with the help of arrayMove.

Tada! Now you know how to implement drag-n-drop in your project. Now go and do it! 🎉 🎉 🎉

What if we have multiple lists of items?

As you can see, the previous example was relatively simple. You basically wrap each of the items in a sortable HOC and wrap it around with sortableContainer and, bingo, you've got basic drag and drop.

But how will we do it with multiple lists? The good news is that react-sortable-hoc provides us with a collection prop so we can differentiate between lists.

First, we should add second array of GIFs:

const [newGifs, setNewGifs] = useState([
  'https://media.giphy.com/media/xiOgHgY2ceKhm46cAj/giphy.gif',
  'https://media.giphy.com/media/3oKIPuMqYfRsyJTWfu/giphy.gif',
  'https://media.giphy.com/media/4ZgLPakqTajjVFOVqw/giphy.gif',
  'https://media.giphy.com/media/3o7btXIelzs8nBnznG/giphy.gif',
]);

If you want to see them before we move next, add the following lines after the SortableGifsContainer closing tag:

{newGifs.map(gif => <Gif key={gif} gif={gif} />)}

Alright, time to replace it with a draggable version.

Implementation is the same as in first example besides one thing — we have added a collection prop to our SortableGif. Of course, you can come up with any name for the collection, just remember, we're gonna need it in for our onSortEnd function.

<h2>Set 2</h2>

<SortableGifsContainer axis="x" onSortEnd={onSortEnd}>
  {newGifs.map((gif,  i) => <SortableGif index={i} key={gif} gif={gif} collection="newGifs" />)}
</SortableGifsContainer>

Next we need to add the collection prop to our first list. I've chosen the name GIFs for the first list of items, but it's up to you!

Now we need to to change our onSortEnd function. Our function received old and new indexes, but we can also destructure a collection from it. Right, exactly the one we've added to our SortableGif.

So all we have to do now is write a JavaScript switch statement to check for the collection name and to modify the right array of GIFs on drag.

const onSortEnd = ({ oldIndex, newIndex, collection }) => {
  switch(collection) {
    case 'gifs':
      setGifs(arrayMove(gifs, oldIndex, newIndex))
      break;
    case 'newGifs':
      setNewGifs(arrayMove(newGifs, oldIndex, newIndex))
      break;
    default:
      break;
  }
}

Time to check it out!

As you can see, we now have two separate lists of GIFs and we can drag and sort. Moreover, they are independent meaning items from different lists won't be mixed up.

Exactly what we wanted to do! Now you know how to create and handle drag and drop with multiple lists of items. Congratulations 🎉

Hope you've enjoyed it as much as I did writing it! If you’d like to reference the complete code, it’s all up on GitHub here. If you have any questions, feel free to contact me via email.

The post Draggin’ and Droppin’ in React appeared first on CSS-Tricks.

Creating Animations Using React Spring

Have you ever needed animation in your React application? Traditionally, implementing animation has not an easy feat to accomplish. But now, thanks to Paul Henschel, we there’s a new React tool just for that. react-spring inherits from animated and react-motion for interpolations, optimized performance, and a clean API.

In this tutorial, we will be looking at two of the five hooks included in react-spring, specifically useSpring and useTrail. The examples we’ll implement make use of both APIs.

If you want to follow along, install react-spring to kick things off:

## yarn
yarn add react-spring

## npm
npm install react-spring --save

Spring

The Spring prop can be used for moving data from one state to another. We are provided with a from and to prop to help us define the animation’s starting and ending states. The from prop determines the initial state of the data during render, while we use to in stating where it should to be after the animation completes.

In the first example, we will make use of the render prop version of creating spring animation.

See the Pen
react spring 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

On initial render, we want to hide a box, and slide it down to the center of the page when a button is clicked. It’s possible to do this without making use of react-spring, of course, but we want to animate the entrance of the box in to view and only when the button is clicked.

class App extends React.Component {
  state = {
    content: false
  }

  displayContent = (e) => {
    e.preventDefault()
    this.setState({ content: !this.state.content })
  }

  render() {
    return (
      <div className="container">
          // The button that toggles the animation
          <div className="button-container">
            <button
              onClick={this.displayContent}
              className="button">
                Toggle Content
            </button>
          </div>
          {
            !this.state.content ?
              (
                // Content in the main container
                <div>
                  No Content
                </div>
              )
            : (
              // We call Spring and define the from and to props
              <Spring
                from={{
                  // Start invisible and offscreen
                  opacity: 0, marginTop: -1000,
                }}
                to={{
                  // End fully visible and in the middle of the screen
                  opacity: 1, marginTop: 0,
                }}
              >
                { props => (
                  // The actual box that slides down
                  <div  className="box" style={ props }>
                    <h1>
                      This content slid down. Thanks to React Spring
                    </h1>
                  </div>
              )}
            </Spring>
            )
        }
      </div>
    )
  }
}

Most of the code is basic React that you might already be used to seeing. We make use of react-spring in the section where we want to conditionally render the content after the value of content has been changed to true. In this example, we want the content to slide in from the top to the center of the page, so we make use of marginTop and set it to a value of -1000 to position it offscreen, then define an opacity of 0 as our values for the from prop. This means the box will initially come from the top of the page and be invisible.

Clicking the button after the component renders updates the state of the component and causes the content to slide down from the top of the page.

We can also implement the above example using the hooks API. For this, we’ll be making use of the useSpring and animated hooks, alongside React’s built-in hooks.

const App = () => {
  const [contentStatus, displayContent] = React.useState(false);
  // Here's our useSpring Hook to define start and end states
  const contentProps = useSpring({
    opacity: contentStatus ? 1 : 0,
    marginTop: contentStatus ? 0 : -1000
  })
  return (
    <div className="container">
      <div className="button-container">
        <button
          onClick={() => displayContent(a => !a)}
          className="button">Toggle Content</button>
      </div>
        {
          !contentStatus ?
            (
              <div>
                No Content
              </div>
            )
          : (
            // Here's where the animated hook comes into play
            <animated.div className="box" style={ contentProps }>
              <h1>
                This content slid down. Thanks to React Spring
              </h1>
            </animated.div>
          )
        }
    </div>
  )
}

First, we set up the state for the component. Then we make use of useSpring to set up the animations we need. When contentStatus is true, we want the values of marginTop and opacity to be 0 and 1, respectively. Else, they should be -1000 and 0. These values are assigned to contentProps which we then pass as props to animated.div.

When the value of contentStatus changes, as a result of clicking the button, the values of opacity and marginTop changes alongside. This cause the content to slide down.

See the Pen
react spring 2
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Trail

The Trail prop animates a list of items. The animation is applied to the first item, then the siblings follow suit. To see how that works out, we’ll build a component that makes a GET request to fetch a list of users, then we will animate how they render. Like we did with Spring, we’ll see how to do this using both the render props and hooks API separately.

First, the render props.

class App extends React.Component {
  state = {
    isLoading: true,
    users: [],
    error: null
  };
  
  // We're using the Fetch <abbr>API</abbr> to grab user data
  // https://css-tricks.com/using-data-in-react-with-the-fetch-api-and-axios/
  fetchUsers() {
    fetch(`https://jsonplaceholder.typicode.com/users`)
      .then(response => response.json())
      .then(data =>
        // More on setState: https://css-tricks.com/understanding-react-setstate/
        this.setState({
          users: data,
          isLoading: false,
        })
      )
      .catch(error => this.setState({ error, isLoading: false }));
  }

  componentDidMount() {
    this.fetchUsers();
  }

  render() {
    const { isLoading, users, error } = this.state;
    return (
      <div>
        <h1>Random User</h1>
        {error ? <p>{error.message}</p> : null}
        {!isLoading ? (
          // Let's define the items, keys and states using Trail
          <Trail
            items={users}
            keys={user => user.id}
            from={{ marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' }}
            to={{ marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }}
          >
          {user => props => (
          <div style={props} className="box">
            {user.username}
          </div>
        )}
      </Trail>
        ) : (
          <h3>Loading...</h3>
        )}
      </div>
    );
  }
}

When the component mounts, we make a request to fetch some random users from a third-party API service. Then, we update this.state.users using the data the API returns. We could list the users without animation, and that will look like this:

users.map(user => {
  const { username, name, email } = user;
  return (
    <div key={username}>
      <p>{username}</p>
    </div>
  );
})

But since we want to animate the list, we have to pass the items as props to the Trail component:

<Trail
  items={users}
  keys={user => user.id}
  from={{ marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' }}
  to={{ marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }}
>
  {user => props => (
    <div style={props} className="box">
      {user.username}
    </div>
  )}
</Trail>

We set the keys to the ID of each user. You can see we are also making use of the from and to props to determine where the animation should start and end.

Now our list of users slides in with a subtle animation:

See the Pen
React Spring - Trail 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

The hooks API gives us access to useTrail hook. Since we are not making use of a class component, we can make use of the useEffect hook (which is similar to componentDidMount and componentDidUpdate lifecycle methods) to fetch the users when the component mounts.

const App = () => {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/users`)
      .then(response => response.json())
      .then(data =>
        setUsers(data)
      )
  }, [])
  
  const trail = useTrail(users.length, {
    from: { marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' },
    to: { marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }
  })

  return (
    <React.Fragment>
      <h1>Random User</h1>
      {trail.map((props, index) => {
        return (
          <animated.div
            key={users[index]}
            style={props}
            className="box"
          >
            {users[index].username}
          </animated.div>
        )
      })}
    </React.Fragment>
  );
}

We have the initial state of users set to an empty array. Using useEffect, we fetch the users from the API and set a new state using the setUsers method we created with help from the useState hook.

Using the useTrail hook, we create the animated style passing it values for from and to, and we also pass in the length of the items we want to animate. In the part where we want to render the list of users, we return the array containing the animated props.

See the Pen
React Spring -Trail 2
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Go, spring into action!

Now you have a new and relatively easy way to work with animations in React. Try animating different aspects of your application where you see the need. Of course, be mindful of user preferences when it comes to animations because they can be detrimental to accessibility.

While you’re at it, ensure you check out the official website of react-spring because there are tons of demo to get your creative juices flowing with animation ideas.

The post Creating Animations Using React Spring appeared first on CSS-Tricks.

Animating Between Views in React

You know how some sites and web apps have that neat native feel when transitioning between two pages or views? Sarah Drasner has shown some good examples and even a Vue library to boot.

These animations are the type of features that can turn a good user experience into a great one. But to achieve this in a React stack, it is necessary to couple crucial parts in your application: the routing logic and the animation tooling.

Let’s start with animations. We’ll be building with React, and there are great options out there for us to leverage. Notably, the react-transition-group is the official package that handles elements entering and leaving the DOM. Let’s explore some relatively straightforward patterns we can apply, even to existing components.

Transitions using react-transition-group

First, let’s get familiar with the react-transition-group library to examine how we can use it for elements entering and leaving the DOM.

Single components transitions

As a simple example of a use case, we can try to animate a modal or dialog — you know, the type of element that benefits from animations that allow it enter and leave smoothly.

A dialog component might look something like this:

import React from "react";

class Dialog extends React.Component {
  render() {
    const { isOpen, onClose, message } = this.props;
    return (
      isOpen && (
        <div className="dialog--overlay" onClick={onClose}>
          <div className="dialog">{message}</div>
        </div>
      )
    );
  }
}

Notice we are using the isOpen prop to determine whether the component is rendered or not. Thanks to the simplicity of the recently modified API provided by react-transition-group module, we can add a CSS-based transition to this component without much overhead.

First thing we need is to wrap the entire component in another TransitionGroup component. Inside, we keep the prop to mount or unmount the dialog, which we are wrapping in a CSSTransition.

import React from "react";
import { TransitionGroup, CSSTransition } from "react-transition-group";

class Dialog extends React.Component {
  render() {
    const { isOpen, onClose, message } = this.props;
    return (
      <TransitionGroup component={null}>
        {isOpen && (
          <CSSTransition classNames="dialog" timeout={300}>
            <div className="dialog--overlay" onClick={onClose}>
              <div className="dialog">{message}</div>
            </div>
          </CSSTransition>
        )}
      </TransitionGroup>
    );
  }
}

Every time isOpen is modified, a sequence of class names changes will happen in the dialog’s root element.

If we set the classNames prop to "fade", then fade-enter will be added immediately before the element mounts and then fade-enter-active when the transition kicks off. We should see fade-enter-done when the transition finishes, based on the timeout that was set. Exactly the same will happen with the exit class name group at the time the element is about to unmount.

This way, we can simply define a set of CSS rules to declare our transitions.

.dialog-enter {
  opacity: 0.01;
  transform: scale(1.1);
}

.dialog-enter-active {
  opacity: 1;
  transform: scale(1);
  transition: all 300ms;
}

.dialog-exit {
  opacity: 1;
  transform: scale(1);
}

.dialog-exit-active {
  opacity: 0.01;
  transform: scale(1.1);
  transition: all 300ms;
}

JavaScript Transitions

If we want to orchestrate more complex animations using a JavaScript library, then we can use the Transition component instead.

This component doesn’t do anything for us like the CSSTransition did, but it does expose hooks on each transition cycle. We can pass methods to each hook to run calculations and animations.

<TransitionGroup component={null}>
  {isOpen && (
    <Transition
      onEnter={node => animateOnEnter(node)}
      onExit={node => animateOnExit(node)}
      timeout={300}
    >
      <div className="dialog--overlay" onClick={onClose}>
        <div className="dialog">{message}</div>
      </div>
    </Transition>
  )}
</TransitionGroup>

Each hook passes the node to the callback as a first argument — this gives control for any mutation we want when the element mounts or unmounts.

Routing

The React ecosystem offers plenty of router options. I’m gonna use react-router-dom since it’s the most popular choice and because most React developers are familiar with the syntax.

Let’s start with a basic route definition:

import React, { Component } from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import Home from '../views/Home'
import Author from '../views/Author'
import About from '../views/About'
import Nav from '../components/Nav'

class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <div className="app">
          <Switch>
            <Route exact path="/" component={Home}/>
            <Route path="/author" component={Author} />
            <Route path="/about" component={About} />
          </Switch>
        </div>
      </BrowserRouter>
    )
  }
}

We want three routes in this application: home, author and about.

The BrowserRouter component handles the browser’s history updates, while Switch decides which Route element to render depending on the path prop. Here’s that without any transitions:

Don’t worry, we’ll be adding in page transitions as we go.

Oil and water

While both react-transition-group and react-router-dom are great and handy packages for their intended uses, mixing them together can break their functionality.

For example, the Switch component in react-router-dom expects direct Route children and the TransitionGroup components in react-transition-group expect CSSTransition or Transition components to be direct children of it too. So, we’re unable to wrap them the way we did earlier.

We also cannot toggle views with the same boolean approach as before since it’s handled internally by the react-router-dom logic.

React keys to the rescue

Although the solution might not be as clean as our previous examples, it is possible to use the libraries together. The first thing we need to do is to move our routes declaration to a render prop.

<BrowserRouter>
  <div className="app">
    <Route render={(location) => {
      return (
        <Switch location={location}>
          <Route exact path="/" component={Home}/>
          <Route path="/author" component={Author} />
          <Route path="/about" component={About} />
        </Switch>
      )}
    />
</BrowserRouter>

Nothing has changed as far as functionality. The difference is that we are now in control of what gets rendered every time the location in the browser changes.

Also, react-router-dom provides a unique key in the location object every time this happens.

In case you are not familiar with them, React keys identify elements in the virtual DOM tree. Most times, we don’t need to indicate them since React will detect which part of the DOM should change and then patch it.

<Route render={({ location }) => {
  const { pathname, key } = location

  return (
    <TransitionGroup component={null}>
      <Transition
        key={key}
        appear={true}
        onEnter={(node, appears) => play(pathname, node, appears)}
        timeout={{enter: 750, exit: 0}}
      >
        <Switch location={location}>
          <Route exact path="/" component={Home}/>
          <Route path="/author" component={Author} />
          <Route path="/about" component={About} />
        </Switch>
      </Transition>
    </TransitionGroup>
  )
}}/>

Constantly changing the key of an element — even when its children or props haven't been modified — will force React to remove it from the DOM and remount it. This helps us emulate the boolean toggle approach we had before and it’s important for us here because we can place a single Transition element and reuse it for all of our view transitions, allowing us to mix routing and transition components.

Inside the animation function

Once the transition hooks are called on each location change, we can run a method and use any animation library to build more complex scenes for our transitions.

export const play = (pathname, node, appears) => {
  const delay = appears ? 0 : 0.5
  let timeline

  if (pathname === '/')
    timeline = getHomeTimeline(node, delay)
  else
    timeline = getDefaultTimeline(node, delay)

  timeline.play()
}

Our play function will build a GreenSock timeline here depending on the pathname, and we can set as many transitions as we want for each different routes.

Once the timeline is built for the current pathname, we play it.

const getHomeTimeline = (node, delay) => {
  const timeline = new Timeline({ paused: true });
  const texts = node.querySelectorAll('h1 > div');

  timeline
    .from(node, 0, { display: 'none', autoAlpha: 0, delay })
    .staggerFrom(texts, 0.375, { autoAlpha: 0, x: -25, ease: Power1.easeOut }, 0.125);

  return timeline
}

Each timeline method digs into the DOM nodes of the view and animates them. You can use other animation libraries instead of GreenSock, but the important detail is that we build the timeline beforehand so that our main play method can decide which one should run for each route.

Success!

I’ve used this approach on lots of projects, and though it doesn't present obvious performance issues for inner navigations, I did notice a concurrency issue between the browser's initial DOM tree build and the first route animation. This caused a visual lag on the animation for the first load of the application.

To make sure animations are smooth in each stage of the application, there’s one last thing we can do.

Profiling the initial load

Here’s what I found when auditing the application in Chrome DevTools after a hard refresh:

You can see two lines: one blue and one red. Blue represents the load event and red the DOMContentLoaded. Both intersect the execution of the initial animations.

These lines are indicating that elements are animating while the browser hasn’t yet finished building the entire DOM tree or it's parsing resources. Animations account for big performance hits. If we want anything else to happen, we’d have to wait for the browser to be ready with these heavy and important tasks before running our transitions.

After trying a lot of different approaches, the solution that actually worked was to move the animation after these events — simple as that. The issue is that we can’t rely on event listeners.

window.addEventListener(‘DOMContentLoaded’, () => {
  timeline.play()
})

If for some reason, the event occurs before we declare the listener, the callback we pass will never run and this could lead to our animations never happening and an empty view.

Since this is a concurrency and asynchronous issue, I decided to rely on promises, but then the question became: how can promises and event listeners be used together?

By creating a promise that gets resolved when the event takes place. That’s how.

window.loadPromise = new Promise(resolve => {
  window.addEventListener(‘DOMContentLoaded’, resolve)
})

We can put this in the document head or just before the script tag that loads the application bundle. This will make sure the event never happens before the Promise is created.

Plus, doing this allows us to use the globally exposed loadPromise to any animation in our application. Let’s say that we don’t only want to animate the entry view but a cookie banner or the header of the application. We can simply call each of these animations after the promise has resolved using then along with our transitions.

window.loadPromise.then(() => timeline.play())

This approach is reusable across the entire codebase, eliminating the issue that would result when an event gets resolved before the animations run. It will defer them until the browser DOMContentLoaded event has passed.

See now that the animation is not kicking off until the red line appears.

The difference is not only on the profiling report — it actually solves an issue we had in a real project.

Wrapping up

In order to act as reminders, I created a list of tips for me that you might find useful as you dig into view transitions in a project:

  • When an animation is happening nothing else should be happening. Run animations after all resources, fetching and business logic have completed.
  • No animation is better than crappy animations If you can’t achieve a good animation, then removing it is a fair sacrifice. The content is more important and showing it is the priority until a good animation solution is in place.
  • Test on slower and older devices. They will make it easier for you to catch spots with weak performance.
  • Profile and base your improvements in metrics. Instead of guessing as you go, like I did, see if you can spot where frames are being dropped or if something looks off and attack that issue first.

That’s it! Best of luck with animating view transitions. Please post a comment if this sparked any questions or if you have used transitions in your app that you’d like to share!

The post Animating Between Views in React appeared first on CSS-Tricks.