Create React Pagination Component With Tailwind CSS

by Alex Johnson 52 views

In this comprehensive guide, we'll walk through the process of creating a dynamic and reusable pagination component using React and Tailwind CSS. Pagination is an essential feature for any application that displays large datasets, allowing users to navigate through content efficiently. We'll cover everything from setting up the basic structure to implementing advanced features like ellipsis for skipped pages and responsive design.

Understanding the Requirements

Before diving into the code, let's clarify the requirements. We aim to build a pagination component that includes the following features:

  • Previous/Next Buttons: Navigation buttons with icons to move to the previous or next page.
  • Numbered Page Buttons: Buttons displaying page numbers for direct access.
  • Ellipsis for Skipped Pages: An ellipsis (...) to indicate skipped pages when there are many pages.
  • Active Page Highlighting: A visual indication of the currently selected page.
  • Props for Customization: Props to handle the current page, total pages, and page change events.
  • Responsive Layout: A layout that adapts to different screen sizes, ensuring a consistent user experience.
  • Tailwind CSS Styling: Styling using Tailwind CSS for a clean and modern look.

Setting Up the Project

First, ensure you have a React project set up with Tailwind CSS. If not, you can create a new project using Create React App and add Tailwind CSS.

npx create-react-app pagination-component
cd pagination-component
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Next, configure Tailwind CSS in your project by updating the tailwind.config.js file:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Also, include the Tailwind directives in your src/index.css file:

@tailwind base;
@tailwind components;
@tailwind utilities;

With the project set up, we can now start building our pagination component.

Creating the Pagination Component

Let’s create a new file named Pagination.tsx inside the components/ui directory. This component will handle the pagination logic and render the necessary UI elements.

Basic Component Structure

Start by defining the basic structure of the component. This includes the component's functional definition and the props it will receive.

// components/ui/Pagination.tsx
import React from 'react';

interface PaginationProps {
  currentPage: number;
  totalPages: number;
  onPageChange: (page: number) => void;
}

const Pagination: React.FC<PaginationProps> = ({ currentPage, totalPages, onPageChange }) => {
  return (
    <div className="flex items-center justify-center">
      {/* Pagination controls will go here */}
    </div>
  );
};

export default Pagination;

In this code, we define the PaginationProps interface to specify the types for the props: currentPage, totalPages, and onPageChange. The Pagination component is a functional component that accepts these props and returns a JSX element. The main container is a div with flexbox classes for centering the content.

Implementing Pagination Logic

Next, we need to implement the core pagination logic. This includes generating the page numbers, handling ellipsis, and determining which buttons to display. Let's create a helper function to generate the page numbers array.

const getPageNumbers = (currentPage: number, totalPages: number): (number | string)[] => {
  const maxButtons = 7; // Maximum number of buttons to display
  const pageNumbers: (number | string)[] = [];

  if (totalPages <= maxButtons) {
    // If total pages are less than or equal to max buttons, show all pages
    for (let i = 1; i <= totalPages; i++) {
      pageNumbers.push(i);
    }
  } else {
    const ellipsis = '...';
    const showLeftDots = currentPage > 3;
    const showRightDots = currentPage < totalPages - 2;

    if (showLeftDots && !showRightDots) {
      const start = totalPages - 4;
      for (let i = start; i <= totalPages; i++) {
        pageNumbers.push(i);
      }
      pageNumbers.unshift(1, ellipsis);
    } else if (!showLeftDots && showRightDots) {
      for (let i = 1; i <= 5; i++) {
        pageNumbers.push(i);
      }
      pageNumbers.push(ellipsis, totalPages);
    } else if (showLeftDots && showRightDots) {
      const middleButtons = [currentPage - 1, currentPage, currentPage + 1];
      pageNumbers.push(1, ellipsis, ...middleButtons, ellipsis, totalPages);
    } else {
      for (let i = 1; i <= Math.min(totalPages, maxButtons); i++) {
        pageNumbers.push(i);
      }
    }
  }

  return pageNumbers;
};

This function generates an array of page numbers and ellipsis based on the current page and total pages. It handles different scenarios to ensure a consistent and user-friendly pagination experience. The maxButtons constant determines the maximum number of page buttons to display.

Rendering the Buttons

Now that we have the logic for generating page numbers, let's render the buttons in the component. We'll add the Previous and Next buttons, as well as the numbered page buttons.

const Pagination: React.FC<PaginationProps> = ({ currentPage, totalPages, onPageChange }) => {
  const pageNumbers = getPageNumbers(currentPage, totalPages);

  const handlePageClick = (page: number) => {
    onPageChange(page);
  };

  const handlePreviousClick = () => {
    if (currentPage > 1) {
      onPageChange(currentPage - 1);
    }
  };

  const handleNextClick = () => {
    if (currentPage < totalPages) {
      onPageChange(currentPage + 1);
    }
  };

  return (
    <div className="flex items-center justify-center">
      <button
        onClick={handlePreviousClick}
        disabled={currentPage === 1}
        className="px-4 py-2 mx-1 bg-white text-gray-700 rounded-md hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
      >
        Previous
      </button>

      {pageNumbers.map((page, index) => (
        <React.Fragment key={index}>
          {
            typeof page === 'number' ? (
              <button
                onClick={() => handlePageClick(page)}
                className={`px-4 py-2 mx-1 rounded-md ${currentPage === page
                  ? 'bg-blue-500 text-white' : 'bg-white text-gray-700 hover:bg-gray-200'}`}
              >
                {page}
              </button>
            ) : (
              <span className="px-4 py-2 mx-1">{page}</span>
            )
          }
        </React.Fragment>
      ))}

      <button
        onClick={handleNextClick}
        disabled={currentPage === totalPages}
        className="px-4 py-2 mx-1 bg-white text-gray-700 rounded-md hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
      >
        Next
      </button>
    </div>
  );
};

In this code, we iterate over the pageNumbers array and render a button for each page number. If the element is an ellipsis, we render a span element instead of a button. The active page button is highlighted with a different background color. We also handle the Previous and Next button clicks, ensuring that the page change events are triggered correctly.

Adding Styles with Tailwind CSS

We've already added some basic styles using Tailwind CSS classes. Let's enhance the styling to match the design requirements. We'll focus on the hover and active states, as well as the overall appearance of the buttons.

<button
  onClick={() => handlePageClick(page)}
  className={`px-4 py-2 mx-1 rounded-md ${currentPage === page
    ? 'bg-blue-500 text-white' : 'bg-white text-gray-700 hover:bg-blue-200'}`}
>
  {page}
</button>

Here, we've updated the button styles to include a hover effect (hover:bg-blue-200) and a distinct style for the active page (bg-blue-500 text-white). You can further customize the styles to match your specific design requirements.

Integrating the Pagination Component

To use the pagination component, import it into your desired component and pass the necessary props. For example:

// Example usage in a parent component
import React, { useState } from 'react';
import Pagination from './components/ui/Pagination';

const MyComponent: React.FC = () => {
  const [currentPage, setCurrentPage] = useState(1);
  const totalPages = 10; // Example total pages

  const handlePageChange = (page: number) => {
    setCurrentPage(page);
    // Fetch data for the new page here
    console.log(`Page changed to ${page}`);
  };

  return (
    <div>
      {/* Content display area */}
      <Pagination
        currentPage={currentPage}
        totalPages={totalPages}
        onPageChange={handlePageChange}
      />
    </div>
  );
};

export default MyComponent;

In this example, we use the useState hook to manage the current page and pass it as a prop to the Pagination component. The handlePageChange function updates the current page and can be used to fetch data for the new page.

Adding Responsiveness

To ensure the pagination component works well on different screen sizes, we need to add responsive styles. Tailwind CSS makes this easy with its responsive modifiers. Let’s adjust the layout for smaller screens.

Adjusting Button Sizes and Spacing

We can use the sm:, md:, lg:, and xl: prefixes to apply styles at different breakpoints. For example, we can reduce the padding and margin on smaller screens.

<button
  onClick={handlePreviousClick}
  disabled={currentPage === 1}
  className="px-2 py-1 mx-1 sm:px-4 sm:py-2 sm:mx-1 bg-white text-gray-700 rounded-md hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
  Previous
</button>

In this code, we’ve reduced the padding and margin for smaller screens using the sm: prefix. This ensures that the buttons don't overflow on mobile devices.

Handling Ellipsis on Smaller Screens

On very small screens, we might want to further simplify the pagination by showing fewer page numbers and more ellipsis. We can adjust the getPageNumbers function to handle this.

const getPageNumbers = (currentPage: number, totalPages: number): (number | string)[] => {
  const maxButtons = 7;
  const pageNumbers: (number | string)[] = [];

  if (totalPages <= maxButtons) {
    for (let i = 1; i <= totalPages; i++) {
      pageNumbers.push(i);
    }
  } else {
    const ellipsis = '...';
    const showLeftDots = currentPage > 3;
    const showRightDots = currentPage < totalPages - 2;

    if (showLeftDots && !showRightDots) {
      const start = totalPages - 4;
      for (let i = start; i <= totalPages; i++) {
        pageNumbers.push(i);
      }
      pageNumbers.unshift(1, ellipsis);
    } else if (!showLeftDots && showRightDots) {
      for (let i = 1; i <= 5; i++) {
        pageNumbers.push(i);
      }
      pageNumbers.push(ellipsis, totalPages);
    } else if (showLeftDots && showRightDots) {
      const middleButtons = [currentPage - 1, currentPage, currentPage + 1];
      pageNumbers.push(1, ellipsis, ...middleButtons, ellipsis, totalPages);
    } else {
      for (let i = 1; i <= Math.min(totalPages, maxButtons); i++) {
        pageNumbers.push(i);
      }
    }
  }

  return pageNumbers;
};

Testing the Component

Finally, it’s crucial to test the pagination component thoroughly. Ensure that all buttons work as expected, the active page is highlighted correctly, and the layout is responsive across different screen sizes. You can use React Testing Library to write unit and integration tests for your component.

Conclusion

In this guide, we've walked through the process of building a dynamic and reusable pagination component using React and Tailwind CSS. We covered everything from setting up the basic structure to implementing advanced features like ellipsis for skipped pages and responsive design. This component can be easily integrated into any React application that requires pagination functionality.

By following these steps, you can create a pagination component that not only enhances the user experience but also improves the overall usability of your application. Remember to customize the styles and logic to fit your specific requirements and design preferences. Creating such components not only elevates your project's usability but also enhances your skills as a developer in building user-friendly interfaces.

For more information on React and Tailwind CSS, visit the official React documentation and Tailwind CSS documentation. These resources offer extensive guides, tutorials, and examples to help you further your understanding and skills.