Let's Build Animated Pagination with React!
Tags: react, javascript, tutorial, webdev
Published: 2020-11-15 19:47:05

Hello there! In this tutorial, we are going to build a universal "smart" (i.e. stateful) pagination React functional Component with transition effect, suitable for listing out dynamic on-page data that doesn’t need a separate URL: users, comments, products, etc. This can be a useful feature in administration panels and comment sections of your projects, when you have a relatively long list of data that you might need to filter, search, and change dynamically.

{% codesandbox react-transition-pagination-final-dtn8y %}
*The final goal*

If you need to have a URL for each page, I would suggest getting the best from your client-side routing library of choice, for instance <a target="blank" href="https://reactrouter.com/">react-router</a>, <a target="blank" href="https://reach.tech/router/">@reach-router</a>, or, in-case of static-site generators, you can look up a <a target="blank" href="https://www.gatsbyjs.com/docs/routing/">Gatsby</a> or <a target="blank" href="https://nextjs.org/docs/routing/introduction">Next.js</a>-specific solution.

Besides that, we are going to touch upon (pun intended) the browser touch events to flip our pages on mobile and tablet devices, examine one of the solutions for zero-dependency replayable animations in React, and see some use cases of <a target="blank" href="https://reactjs.org/docs/refs-and-the-dom.html">refs</a>, as well as <a target="blank" href="https://reactjs.org/docs/events.html#overview">stopPropagation() method</a> of the React SyntheticEvent.

For convenience, I’ve created a small NPM module <a target="blank" href="https://www.npmjs.com/package/react-animated-pagination">react-animated-pagination</a> as well as <a target="blank" href="https://react-animated-pagination.zdcreatech.com/">a demo website for it</a> that you can refer to and customize in your projects.

__Prerequisites__: intermediate JavaScript knowledge, a solid understanding of React and React Hooks. _This post is aimed at fellow Reacters who are already familiar with the library's key features and might have already built some amazing projects, but want to see more examples on parts not so extensively covered in the official docs, like usage of refs and event propagation._

# Problem and Objective Overview

In a React application, a typical use case for pagination is listing a set of items stored in the application's (or component’s) state. Usually, we need to iterate over an Array of JavaScript Objects, and create a React Component for each Object with a defined `key` prop and some `itemName` prop specific for the Component (`post` for `PostComponent`, `user` for `UserCard`, etc). 

For instance, let's say we have fetched some posts from our API, and want display them as `PostComponent`s that take specific handler methods as props:

```jsx
const ContainerComponent = ({ posts, handleRemoveFromFeed, ... }) => {
...
  return (
    <div className="mainContainerDiv">
...
  {
    posts && posts.map(post => (
      <PostComponent>
        key={post.uniqueId}
        post={post}
        handleRemoveFromFeed={handleRemoveFromFeed}
        handleUpvote={handleUpvote}
        handleDownvote={handleDownvote}
        handleAddComment={handleAddComment}
      </PostComponent>
    ))
  }
...
```

Now, our posts Array is listed alright, with all the handlers working correctly. We defined the key prop, so that <a target="blank" href="https://reactjs.org/docs/reconciliation.html#keys">the React Diffing Algorithm</a> knows about the rendered elements (in case some of them gets removed from the state, with the `handleRemoveFromFeed` handler, for example), the value of the `post` prop is the value of the item in the current iteration of the Array.

But turns out, we got hundreds of posts we need to display! We'd better present them neatly in a "page-like" format. Adding basic pagination is not that complex, we can simply add some logic to our parent component with the `useState` and `useEffect` Hooks:

```jsx
// This snippet is not a fully working example, 
// just an overview of one of the solutions
import React, { useState, useEffect } from "react";

const ContainerComponent = ({ posts, handleRemoveFromFeed, ... }) => {
...
  // The number of items to display
  const itemsPerPage = 5;
  // The Array of subarrays with posts divided according to the value of itemsPerPage
  const [pages, setPages] = useState([]);
  // The index of pages Array - this will be the current visible page
  const [currentPage, setCurrentPage] = useState(0);

  // A handler for changing the page
  const handlePageChange = (pageNo) => {
    setCurrentPage(pageNo);
  };

  // In the first useEffect Hook, we assemble and re-assemble
  // pages Array that contains several subArrays of our passed-in
  // posts Array, every time the posts prop changes 
  // (e.g. on post being removed)
  useEffect(() => {
    let workingPages = [];
    let workingPagesCurrentIndex = 0;

    if (posts) {
      posts.forEach(post => {
        if (
          workingPages[workingPagesCurrentIndex] &&
          workingPages[workingPagesCurrentIndex].length === itemsPerPage
        )
          workingPagesCurrentIndex++;

        if (workingPages[workingPagesCurrentIndex] === undefined)
          workingPages[workingPagesCurrentIndex] = [];

        workingPages[workingPagesCurrentIndex].push(item);
      });
    }

    setPages([...workingPages]);
  }, [posts, setPages]);

  // This Hooks runs every time when currentPage index changes 
  // withhandlePageChange() or when the number of pages changes
  // (for instance, when we delete an item)
  useEffect(() => {
    if (!pages[currentPage]) {
      setCurrentPage(currentPage - 1 > -1 ? currentPage - 1 : 0);
    }
  }, [currentPage, pages]);

  return (
    <div className="mainContainerDiv">
  ...
  {
  /*
  Buttons for handling changing the page
  */
  }
  <button 
    onClick={() => handlePageChange(currentPage - 1)}
    disabled={currentPage === 0 ? true : false}
  >
    Previous
  </button>
  <button 
    onClick={() => handlePageChange(currentPage + 1)}
    disabled={currentPage === pages.length - 1 ? true : false}
  >
    Next
  <button>
  {
  /*
  Iterate over the current subarray of posts to display them
  */
  }
  {
    pages.length && pages[currentPage] && pages[currentPage].map(post => (
      <PostComponent>
        key={post.uniqueId}
        post={post}
        handleRemoveFromFeed={handleRemoveFromFeed}
        handleUpvote={handleUpvote}
        handleDownvote={handleDownvote}
        handleComment={handleComment}
      </PostComponent>
    ))
  }
  ...
  );
}
```

This logic provides us with a basic pagination solution: to divide our Array-to-iterate state into an Array of subarrays called `pages`, with a state (`currentPage`) that indicates the currently visible section of the Array. 

Defined inside the `ContainerComponent`, even this crude solution looks pretty huge, and let's not forget that we have some much to add! We need to create buttons that indicate the current page and other available pages to navigate the pagination, and we'd also better to have both top and bottom navigation for convenience. We definitely need to create a separate `Pagination` Component in order to avoid a total mess!

Extracting this logic in a separate stateful Component is not hard: it will take the posts to iterate over as props, and will contain all the buttons, navigation and styles, easy! However, here comes the catch: how do we pass all the handlers and universal data our paginated components might require? Of course, if we use Redux, MobX, or some other State Management library, this is not a big deal: our Post Components can receive all their handlers and required data from the Application's main state. But not all the projects (even relatively large ones) need Redux, and even not all Redux-based projects have all their state lifted up to Redux. Moreover, our `Pagination` right now is tailored exclusively for rendering `PostComponent`s, and what if we need to paginate some, say, `UserCard`s or `CommentComponent`s? Do they need to have a special version of the `PaginationUserCard` or `PaginationCommentComponent`? __Definitely not__. 

We need to create a __universal__ `Pagination` Component suitable for the majority of cases. It will take the React Component to render (`UserCard`, `PostComponent`, etc.) as props, as well as some parameters, like, how many items are allowed per page, and whether we need to show bottom and top navigation.

We will build a small TODO-ish application that fetches JSON data - an Array of posts - from the <a target="blank" href="https://jsonplaceholder.typicode.com/">jsonplaceholder API</a>, converts it into an array of JavaScript Objects to be stored in the component’s state, iterates through the array and displays each item as a React component, which can be deleted on double click. Instead of making an immense list, the items will be neatly paginated with a special `Pagination` component we’re going to implement, the number of pages will be dynamic, changing when we delete items. Going back and forth between pages will be visually emphasized with a transition effect and support swipes. We will also add a universal prop `totalLikes` required by all the paginated items.

# Basic Setup

At this step, we will create the base of our application, that will be able to fetch an array of posts from the jsonplaceholder API, store it in the component’s state, display them on the page by creating instances of `Post` component, handle click on a `Post`s "like button", and delete a post on double click.

Create a new React project

```bash
npx create-react-app pagination-example
```

And inside the `src` folder create a new file `Post.js`. Add the following code:

```jsx
import React from "react";

const Post = ({ 
post, handleDelete, handleLikePost, totalLikes
}) => {

  return (
    <div
      className={`post`}
      // delete post with double-click on the post's container div
      onDoubleClick={(e) => {
        handleDelete(post.id);
      }}
    >
      <h3>{post.title}</h3>
      <p>{post.body}</p>
      {/* Show how many likes the post has */}
      <div>
        Likes: {post.likes ? post.likes : 0}{" "}
        {post.likes && totalLikes ? `out of ${totalLikes}` : ""}
      </div>
      <button
        className="post__likeBtn"
        // Like post on click
        onClick={(e) => {
          handleLikePost(post.id);
        }}
        // Avoid propagating the double click on the button 
        // so the post won't get deleted accidently 
        onDoubleClick={(e) => {
          e.stopPropagation();
        }}
      >
        <span role="img" aria-label="like button">
          💖
        </span>
      </button>
    </div>
  );
};

export default Post;

```

The structure of the above component is typical: it’s a “dumb” stateless component that takes all the data it needs to display from the `post` prop and the actual handler for deleting the post is passed through the `handleDelete` prop.

The only unusual part might be this handler:

```
onDoubleClick={(e) => {
  e.stopPropagation();
}}
```

Here we evoke a special method of React's synthetic event `e.stopPropagation()`, to avoid firing `double-click` event on the button's parent element. You've probably already encountered the `e.preventDefault()` method, usually implemented when submitting React-controlled forms with AJAX request, this one does roughly the same: overrides the default browser behavior. We will return to this method once we implement touch events in our `Pagination`.

__Please note:__ here we use `onDoubleClick` event to handle delete logic for a mere demonstration and learning purpose. Putting something unobvious like this on double-click or double-tap might result in a __horrifying__ user experience.

Let’s see our brand-new component in action. Open `App.js` file, delete all the initial code, and add the following:

```jsx
import React, { useEffect, useState } from "react";

import Post from "./Post";

export default function App() {
  // Loading indicator state
  const [isLoading, setIsLoading] = useState(false);
  // Posts state
  const [posts, setPosts] = useState([]);
  // Likes state
  const [totalLikes, setTotalLikes] = useState(0);

  // Handle delete post using Array.filter() higher order function
  const handleDelete = (id) => {

    // In a real-world application we would probably track the changing
    // number of likes with useEffect() Hook, but since we are in full
    // control over the data, we can directly set totalLikes here for simplicity
    let postTodelete = posts.find((p) => p.id === id);
    if (postTodelete.likes && totalLikes) {
      setTotalLikes((totalLikes) => totalLikes - postTodelete.likes);
    }

    // Note that we don't mutate the original state
    let postsFiltered = [...posts.filter((p) => p.id !== id)];

    setPosts((posts) => [...postsFiltered]);
  };

  // Once again, here we simplify the "liking logic" greatly
  const handleLikePost = (id) => {
    let workingPosts = [...posts];

    workingPosts.find((p) => p.id === id).likes
      ? workingPosts.find((p) => p.id === id).likes++
      : (workingPosts.find((p) => p.id === id).likes = 1);

    setPosts((posts) => [...workingPosts]);
    setTotalLikes((totalLikes) => totalLikes + 1);
  };

  // Fetch the data from API on the first render of the App
  useEffect(() => {
    const fetchPosts = async () => {
      try {
        setIsLoading(true);

        const posts = await fetch(
          "https://jsonplaceholder.typicode.com/posts",
          {
            method: "GET"
          }
        ).then((res) => res.json());

        setPosts([...posts]);
        setIsLoading(false);
      } catch (err) {
        console.log(err);
        setIsLoading(false);
      }
    };

    fetchPosts();
  }, []);

  // As soon is isLoading is equal to false and posts.length !== 0
  // we iterate over the huge Array of Objects to render Post components
  // on each iteration
  return (
    <div className="App">
      <h1>React Simple Pagination</h1>
      <h2>Basic setup</h2>
      <h3>Total Likes: {totalLikes ? totalLikes : 0}</h3>
      {isLoading && posts.length === 0 ? (
        <div>Loading...</div>
      ) : (
        posts &&
        posts.map((post) => (
          <Post
            key={post.id}
            post={post}
            handleDelete={handleDelete}
            handleLikePost={handleLikePost}
            totalLikes={totalLikes}
          />
        ))
      )}
    </div>
  );
}

```

We defined a stateful `App` component, with the state being a `posts` Array, `isLoading` boolean, and `totalLikes` number. We defined a handler for deleting a post from the state array via the Array.filter() and also a handler for "liking" a post. Inside `useEffect` (with an empty array `[]` as the second parameter to run it only on the initial render), we defined and invoked asynchronous function `fetchPosts()` that sets the `isLoading` to `true` when the loading process starts, sets it to `false` when it finishes, and sets the `posts` to the response of the request. The function code is wrapped into a `try...catch` block. If you need a reference to fetching data with React Hooks, please, check out this wonderful <a target="blank" href="https://www.robinwieruch.de/react-hooks-fetch-data">article</a> by Robin Wieruch.

In the `return` part we first check if our load has started, and display a “Loading...” message to the screen. Once `isLoading` is set to false, we iterate through the state array of posts with `.map()` method and "return" a `Post` component for each item of the array, passing the item itself as the `post` prop, `.id` property for its key, `handleDelete` and `handleLikePost` handlers for the respective prop.

Launch the project, and open the browser tab. Run the application with `npm start`, the result should look something like this:

{% codesandbox react-transition-pagination-basic-setup-ed75e %}

Try and double click some posts to see them disappear, check if the "liking" logic functions correctly. 

Everything’s working, but our page is inappropriately huge! Let’s fix this by implementing the `Pagination` component.

# Building Pagination component

Create a new file called `Pagination.js` that will export the `Pagination` component, and the `pagination.css` file containing all the styles needed by the `Pagination`.

In `Pagination.js` add the following code:

```jsx
import React, { useState, useEffect, Fragment } from "react";

import "./pagination.css";

const Pagination = ({
  items,
  itemsOnPage,
  entryProp,
  children
}) => {
  // This will be our state for handling paginated items
  const [pages, setPages] = useState([]);
  // This will hold the currently visible part of the paginated items
  const [currentPage, setCurrentPage] = useState(0);

  // A simple handler for setting the currently visible page
  const handlePageChange = (pageNo) => {
    setCurrentPage(pageNo);
  };

  // Here we re-assembly the pages state on the first render and 
  // every the length of items array or itemsOnPage number change
  useEffect(() => {
    let itemsPerPage = itemsOnPage ? itemsOnPage : 5;
    let workingPages = [];
    let workingPagesCurrentIndex = 0;

    if (items) {
      items.forEach((item) => {
        if (
          workingPages[workingPagesCurrentIndex] &&
          workingPages[workingPagesCurrentIndex].length === itemsPerPage
        )
          workingPagesCurrentIndex++;

        if (workingPages[workingPagesCurrentIndex] === undefined)
          workingPages[workingPagesCurrentIndex] = [];

        workingPages[workingPagesCurrentIndex].push(item);
      });
    }

    setPages([...workingPages]);
  }, [items, itemsOnPage, setPages]);

  // If we were on a page that no longer exists,
  // "redirect" to the previous page
  useEffect(() => {
    if (!pages[currentPage]) {
      setCurrentPage(currentPage - 1 > -1 ? currentPage - 1 : 0);
    }
  }, [currentPage, pages]);

  return (
    <div className="pagintaion__paginationContainer">
    </div>
  );
}

export default Pagination;
```

Before we jump into the `return` part, let's recap the code above: basically, it is the same logic we've discussed in the "Problem and Objective" part. We have some state that will hold paginated data, it's pretty dynamic since it is re-assembled in useEffect(), the `handlePageChange()` handler's logic is pretty straightforward, as well. 

What makes it different, is the `entryProp`in the Component's arguments' list. This one will allow us to make the component universal and suitable for many use cases. Let's have a look at how it works!

In the `Pagination`'s `return` statement add the following code:

```jsx
<div className="pagintaion__paginationContainer">
  {/* 
    Here we define basic controls for our pagination: first amd previous buttons,
    the numbered buttons with active classname, and next and last buttons.
    You can use any icons 
    */}
  <div className="paginationContainer__topNavControls paginationControls">
      <button
        className="paginationControls__arrowBtn"
        onClick={() => handlePageChange(0)}
        disabled={currentPage === 0 ? true : false}
      >
      First
      </button>
      <button
        className="paginationControls__arrowBtn"
        onClick={() => handlePageChange(currentPage - 1)}
        disabled={currentPage === 0 ? true : false}
      >
      Prev
      </button>
      {/* 
        Here we iterate over the pages to render the numbered buttons
        The logic is pretty straightforward, here we use string literals
        and inidices to enumerate the buttons and also to hide some buttons
        if there are too many of them
      */}
      {pages &&
        pages.map((page, index) => (
          <button
            className={`paginationContols__pageNoBtn
                    ${
                      index === currentPage
                        ? "paginationContols__pageNoBtn--active"
                        : ""
                    }
                    ${
                      pages.length > 10 &&
                      index !== 0 &&
                      index !== pages.length - 1 &&
                      (currentPage > index
                        ? currentPage - index > 3
                        : index - currentPage > 3)
                        ? "paginationContols__pageNoBtn--hidden"
                        : ""
                    }
                    ${
                      pages.length > 10 &&
                      index !== 0 &&
                      index !== pages.length - 1 &&
                      currentPage > index &&
                      currentPage - index === 3
                        ? "paginationContols__pageNoBtn--dotsBefore"
                        : ""
                    }
                    ${
                      pages.length > 10 &&
                      index !== 0 &&
                      index !== pages.length - 1 &&
                      index > currentPage &&
                      index - currentPage === 3
                        ? "paginationContols__pageNoBtn--dotsAfter"
                        : ""
                    }
                    `}
            key={index}
            onClick={() => handlePageChange(index)}
            disabled={index === currentPage}
          >
            {index + 1}
          </button>
        ))}
      <button
        className="paginationControls__arrowBtn"
        onClick={() => handlePageChange(currentPage + 1)}
        disabled={currentPage === pages.length - 1 ? true : false}
      >
        Next
      </button>
      <button
        className="paginationControls__arrowBtn"
        onClick={() => handlePageChange(pages.length - 1)}
        disabled={currentPage === pages.length - 1 ? true : false}
      >
        Last
      </button>
    </div>
  {/* 
    Here comes the main catch for making our component universal:
    instead of directly passing the children Component to render, 
    we *clone* it with handler props passed from the Parent, while
    the actual "meat" of the component is passed here
  */}
  <div className={`paginationContainer__currentPageDiv`}>
    {pages.length &&
      pages[currentPage] &&
      pages[currentPage].map((item, index) => {
        let objectToClone = {};
        objectToClone[entryProp] = item;
        return (
          <Fragment key={item.id ? item.id : index}>
            {React.cloneElement(children, objectToClone)}
          </Fragment>
        );
      })}
  </div>
</div>
```

In the code above, we iterate through the pages and render control buttons, as well as the `paginationContainer__currentPageDiv` div. Here our component becomes universal: we use a special React method <a target="blank" href="https://reactjs.org/docs/react-api.html#cloneelement">React.cloneElement()</a> that allows us to **merge** the passed-in Children Component (e.g. our `Post` component) with the current Object in iteration, in which we assign one of the properties to the value of `item` in iteration. We can name this Object's property with the required prop name using the <a target="blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors">bracket notation</a> property accessor to which we pass the `entryProp` string. 

```jsx
objectToClone[entryProp] = item;
```

This little technique allows the `Pagination` Component to be used with virtually any Component, if it has an "entry point", hence the `entryProp` name.

Let's add some styling to the `pagination.css`

```css
.paginationControls {
  display: flex;
  flex-direction: row;

  flex-wrap: wrap;

  margin-left: auto;
  margin-right: auto;
  justify-content: center;
}

.paginationContols__pageNoBtn {
  display: block;

  background: transparent;
  border: transparent;

  min-width: 2em;

  cursor: pointer;
}
.paginationContols__pageNoBtn--active {
  border: 1px blue solid;
}
.paginationContols__pageNoBtn--hidden {
  display: none;
}
.paginationContols__pageNoBtn--dotsAfter::after {
  content: " ... ";
  color: black;
}
.paginationContols__pageNoBtn--dotsBefore::before {
  content: " ... ";
  color: black;
}

.paginationControls__arrowBtn {
  display: block;

  background: transparent;
  border: transparent;

  cursor: pointer;
}
```

and implement our `Pagination` in the `App` Component. Rewrite the `App.js` in the following way:

```jsx
...
import Pagination from "./Pagination";
...
export default function App() {
...
  return (
    <div className="App">
      <h1>React Simple Pagination</h1>
      <h2>This pagination is dynamic</h2>
      <h3>Total Likes: {totalLikes ? totalLikes : 0}</h3>
      {
      isLoading && posts.length === 0 
      ? (
        <div>Loading...</div>
      ) 
      : (
        <Pagination
          itemsOnPage={5}
          items={posts}
          entryProp="post"
          children={
            <Post
              handleDelete={handleDelete}
              handleLikePost={handleLikePost}
              totalLikes={totalLikes}
            />
          }
        />
      )
      }
    </div>
  );
}
```

Instead of iterating over the `posts` Array, we pass it to our brand-new `Pagination` Component. The `entryProp` is "post", and we pass `Post` as the `children` prop with all other props directly passed here, except for the entry one.

Let's test our pagination! The result should be something like this:

{% codesandbox react-transition-pagination-basic-pagination-0pwxr %}

Hey-ya! It works, but looks slightly dull. Give us some action!

# Adding Page Transitions

Jokes aside, animations can be a really important part of the UX. Not only it helps developers to flex their front-end skills, but it is also an important tool for telling the end-user what is going on. Without any transitions, our `Pagination` might provoke some headaches instead of providing convenience. 

There is a ton of ways to make animations with React, and many of them rely heavily on external libraries. But for something as simple as making some page transitions, it is definitely not a must. Let's add some transitions with the help of CSS3 Animations, React refs and <a target="blank" href="https://reactjs.org/docs/events.html#animation-events">Animation events</a>.

Add the following code to the `pagination.css` file, those are some really basic transition animations:

```css
@keyframes nextPage {
  0% {
    opacity: 0;
    transform: translate(10em, 0);
  }

  100% {
    opacity: 1;
    transform: translate(0, 0);
  }
}

@keyframes prevPage {
  0% {
    opacity: 0;
    transform: translate(-10em, 0);
  }

  100% {
    opacity: 1;
    transform: translate(0, 0);
  }
}
```

Now, let's return to the `Pagination.js`. Import `useRef` Hook

```
import React, { useState, useEffect, Fragment, useRef } from "react";
```

Refs are sometimes called an "escape hatch" in React applications. The "React way" of building software is __declarative__: the library abstracts a _huge_ portion of written code when developing complex applications, and gives us an opportunity to reason about different parts of it in a component-scoped way, without the need to write lines and lines of `document.createElement(...)`'s. In order to make the application work predictably, direct DOM tree manipulation should be avoided. However, sometimes we still need to access an HTML element directly, __imperatively__. Here come the `ref` that help us to avoid an absolute mess with random attempts to `document.getElementById()`. 

In the `Pagination` component, add the following code and rewrite the `handlePageChange` handler as follows:

```jsx
const Pagination = ({...}) =>
...
  let currentPageRef = useRef(null);

  const handlePageChange = (pageNo) => {
    if (currentPage > pageNo) {
      currentPageRef.current.style.animation = "prevPage .5s forwards";
    } else {
      currentPageRef.current.style.animation = "nextPage .5s forwards";
    }
    setCurrentPage(pageNo);
  };
...
      <div
        className={`paginationContainer__currentPageDiv`}
        ref={currentPageRef}
...
```

We created a new ref called `currentPageRef`, and made it a ref of the `paginationContainer__currentPageDiv` `div`, the actual element will now be available at `currentPageRef.current` property. We added some logic to the handler, so we can add our animation from the stylesheet in different cases.

However, if we try this code out right now, it will disappoint us: the animation runs only once when flipping through several next or previous pages. We need a way to "unplug" the animation and then add it once again.

There are several ways to re-run CSS3 animations with React, some of them might be a little hacky and not so reliable (like, for instance, using `myRef.current.dashOffset` statement to signal React that something has changed), so it might be better to use one of the React's animation events: `onAnimationEnd()` that will fire as soon as the element's animation finishes.

Add the following handler to the `paginationContainer__currentPageDiv`:

```jsx
...
      <div
        className={`paginationContainer__currentPageDiv`}
        ref={currentPageRef}
        onAnimationEnd={() => {
          if (currentPageRef.current) {
            currentPageRef.current.style.animation = "";
          }
        }}
...
```

And test our application once again. The result should be:

{% codesandbox react-transition-pagination-transition-teh0r %}

Now we're talking! Please also note that I've added some <a target="blank" href="https://icons.getbootstrap.com/">Bootstrap SVG icons</a> for the pagination control buttons. You can add these icons to your project, just copy and paste them from the sandbox, or you can make some of your own!

# Swipe It!

In the mobile-first age, our Pagination definitely better have some touch support! Let's make it swipeable with `Touch` events.

In the `Pagination` component's body add the following code:

```jsx
  // Touch event handling
  // This will signal that the page is being swiped
  const [isDragging, setIsDragging] = useState(false);
  // This will be the point of the initial touch
  const [initialTouch, setInitialTouch] = useState(0);
  // These positions are needed to determine whether to move the page or not,
  // as well as to decide of the page should be flipped
  const [posLeft, setPosLeft] = useState(0);
  const [prevLeft, setPrevLeft] = useState(0);

  // This object will hold the current page container's style
  const divStyle = {
    position: isDragging ? "relative" : "static",
    left: isDragging ? posLeft : 0
  };

  // onTouchStart we signal our container to become position: relative, so that
  // the left property affects its position
  // We also set the initialTouch state and the prevLeft state
  const _onTouchStart = (event) => {
    setIsDragging(true);
    setInitialTouch(event.nativeEvent.touches[0].clientX);

    const { left } = extractPositionDelta(event.nativeEvent.touches[0]);

    if (posLeft + left <= 0) {
      setPosLeft(posLeft + left);
    }
  };

  // Here we decide if the page should be moved, 30 might be a good balance 
  // between too stiff and too sensitive
  const _onTouchMove = (event) => {
    if (!isDragging) {
      return;
    }
    const { left } = extractPositionDelta(event.nativeEvent.touches[0]);

    if (Math.abs(posLeft) + Math.abs(left) > 30) {
      setPosLeft(posLeft + left);
    }
  };

  // When the use removes finger from the screen, we need to determine if 
  // his or her intention was to flip the page; once again, 30 works well
  // In the end we set our state to the initial values
  const _onTouchEnd = (event) => {
    setIsDragging(false);

    let delta = Math.abs(prevLeft) - Math.abs(posLeft);

    if (delta < -30 && posLeft < initialTouch) {
      if (pages[currentPage + 1]) handlePageChange(currentPage + 1);
    } else if (delta > 30 && posLeft > initialTouch) {
      if (pages[currentPage - 1]) handlePageChange(currentPage - 1);
    }

    setPosLeft(0);
    setPrevLeft(0);
    setInitialTouch(0);
  };

  const extractPositionDelta = (event) => {
    const left = event.clientX;

    const delta = {
      left: left - prevLeft
    };

    setPrevLeft(left);

    return delta;
  };
```

Here are our handlers for `Touch` events. Let's add them to the container `div`:

```jsx
      <div
        ref={currentPageRef}
        className={`paginationContainer__currentPageDiv`}
        onAnimationEnd={() => {
          if (currentPageRef.current) {
            currentPageRef.current.style.animation = "";
          }
        }}
        style={divStyle}
        onTouchStart={_onTouchStart}
        onTouchMove={_onTouchMove}
        onTouchEnd={_onTouchEnd}
        onTouchCancel={_onTouchEnd}
      >
```

Now our Pagination can be swiped! Try it out on a mobile screen or in the developer tools simulation.

Our Pagination is all fancy, but our `Post` is kinda lame. Moreover, deleting a post behaves weirdly on a touch screen now! As a little bonus, let's cheer it up, and add some animations to a `Post` leaving the state!

Crate a file `post.css` and add the following code:

```css
.post {
  transition: 0.3s ease-in-out;
}
.post__likeBtn {
  display: block;
  margin-left: auto;
  margin-right: auto;
  margin-top: 0.5em;

  height: 3em;
  width: 3em;

  cursor: pointer;
}

.post--deleting--left {
  animation: postDeletedLeft 0.5s forwards;
}

@keyframes postDeletedLeft {
  0% {
    opacity: 1;
    transform: translate(0, 0);
  }
  100% {
    opacity: 0;
    transform: translate(-10em, 0);
    display: none;
  }
}
```

Here we defined some basic animation for our post being deleted. Now, rewrite `Post.js` in the following way:

```jsx
import React, { useState, useEffect, useRef } from "react";

// import CSS
import "./post.css";

const Post = ({ post, handleDelete, handleLikePost, totalLikes }) => {
  // Now, onClick we will signal the post that it is being deleted,
  // instead of invoking handleDelete() directly
  const [isDeleting, setIsDeleting] = useState(false);

  // We will need a ref to a timeout so that our component works correctly
  let timeoutRef = useRef(null);

  // This will be the handler on the double-click
  const deletePost = () => {
    setIsDeleting(true);
  };

  // This will be the handler on touch screens
  // We use e.stopPropagation(); to avoid messing app the pagination's
  // Touch event logic
  // Delete on double tap
  const [firstTap, setFirstTap] = useState("");
  let touchRef = useRef(null);

  const _onTouchEnd = (e) => {
    if (!firstTap) {
      setFirstTap(new Date().getTime());
      timeoutRef.current = setTimeout(() => {
        setFirstTap("");
      }, 200);
      return;
    } else if (firstTap && timeoutRef.current) {
      e.stopPropagation();
      setFirstTap("");
      setIsDeleting(true);
    }
  };

  // Here we use the timeoutRef to delete the post after the animation runs
  useEffect(() => {
    if (isDeleting) {
      timeoutRef.current = setTimeout(() => handleDelete(post.id), 500);
    }
  }, [isDeleting]);

  // Unmount cleanup to avoid memory leaks
  useEffect(() => () => clearTimeout(timeoutRef.current), []);

  useEffect(() => clearTimeout(touchRef.current), []);

  return (
    <div
      // xchange the className to run the animation
      className={`post ${isDeleting ? `post--deleting--left` : ""}`}
      // this one for mouse double-click
      onDoubleClick={() => {
        deletePost();
      }}
      // this one for touch screen double-tap
      onTouchEnd={(e) => _onTouchEnd(e)}
    >
      <h3>{post.title}</h3>
      <p>{post.body}</p>
      <div>
        Likes: {post.likes ? post.likes : 0}{" "}
        {post.likes && totalLikes ? `out of ${totalLikes}` : ""}
      </div>
      <button
        className="post__likeBtn"
        onClick={(e) => {
          handleLikePost(post.id);
        }}
        // run e.stopPropagation(); to avoid firing delete event
        onDoubleClick={(e) => {
          e.stopPropagation();
        }}
        onTouchEnd={(e) => {
          e.stopPropagation();
        }}
      >
        <span role="img" aria-label="like button">
          💖
        </span>
      </button>
    </div>
  );
};

export default Post;
```

Now check out the browser, it should be something like this:

{% codesandbox react-transition-pagination-final-dtn8y %}

Excellent! Our pagination is working! You can also check out some additional parameters in the sandbox, like `topNav` and `bottomNav` that determine top and bottom controls respectively. 

# Conclusion

Great! It certainly was not a short tutorial, but I hope it was productive: we've touched upon several React techniques which are not that widely covered, but can be quite useful in the real-world applications. We've seen `Touch` events in React in action, learned one of the usages of the `onAnimationEnd()`, and saw how refs can help us to escape the declarative React code to achieve some cool effects. And, the last but not least, we've built a dynamic pagination component that you can use in your projects. 

If you need inspiration, you can check out some <a target="blank" href="https://react-animated-pagination.zdcreatech.com/">pagination examples</a> I've made for the NPM module, all of them have their source code in the `example` directory in the GitHub repo. 

Hope you've enjoyed this tutorial, would really appreciate hearing from you!

Have a good one!