DEV Community

V.D
V.D

Posted on • Originally published at javascript.plainenglish.io on

React: Abstract Design Pattern-DRY & Single Shared Responsibility(Part-2)

React: Abstract Design Pattern-DRY & Single Shared Responsibility(Part-2)

Understanding the Abstract Method pattern in React and exploring how it can be implemented in your applications.

img

This is in continuation with the last article on React — Abstract Design pattern. This article is basically on the potential areas where performance issues could arise.Link to last article

In continuation with the last post code, there are a few potential areas where performance issues could arise,

  1. Rendering large datasets : If the transactions array contains a large number of items, rendering them all at once can impact performance. To mitigate this, consider implementing pagination or virtualization techniques to load and render only the visible portion of the dataset.
  2. Dynamic function creation : The getFormatAmount function dynamically creates the formatAmount function based on the transaction type. While this approach provides flexibility, it can impact performance if the function creation process becomes complex or computationally expensive. Ensure that the function creation logic remains simple and efficient to avoid any performance bottlenecks.
  3. Complex transaction processing : If the processing or manipulation of transaction data within the TransactionItem component becomes computationally intensive, it may lead to performance issues. Analyze the operations performed on each transaction and optimize them as necessary. Consider leveraging memoization or caching techniques to avoid unnecessary calculations or redundant operations.
  4. Inefficient rendering updates: If there are frequent updates to the transactions array or individual transactions, it can trigger unnecessary re-renders. To optimize this, ensure that the transactions array and its items are immutably updated to prevent unintended re-renders. Additionally, consider implementing more granular component updates using React's shouldComponentUpdate or React.memo to prevent unnecessary rendering of components that have not changed.

It’s important to profile and analyze the performance of your application using tools like React DevTools, Chrome DevTools, or other performance monitoring tools. By identifying and addressing specific performance bottlenecks, you can optimize the code and improve the overall performance of your financial app.

To avoid potential performance issues in the provided code, here are some suggestions:

  1. Implement pagination or virtualization: If the transactions array contains a large number of items, consider implementing pagination or virtualization techniques. Instead of rendering all transactions at once, load and render only a subset of transactions based on the current viewport or user interaction. This approach improves initial load time and reduces rendering overhead.
  2. Optimize the dynamic function creation : The getFormatAmount function dynamically creates the formatAmount function based on the transaction type. To optimize performance, ensure that the function creation logic remains lightweight and efficient. Avoid complex computations or heavy operations within the function creation process. If possible, precompute or memoize the format functions to avoid repetitive calculations.
  3. Streamline transaction processing: Analyze the operations performed on each transaction within the TransactionItem component. Look for opportunities to optimize data processing and manipulation. Use efficient algorithms and data structures to minimize computational overhead. Consider memoization techniques to cache expensive calculations or avoid redundant operations.
  4. Use immutability and granular updates : Ensure that the transactions array and its items are updated in an immutable manner. Avoid mutating the original array directly, as it can lead to unnecessary re-renders. Instead, create new arrays or objects when making updates. Additionally, utilize React's shouldComponentUpdate lifecycle method or the React.memo higher-order component to prevent unnecessary re-renders of components that have not changed.
  5. Profile and optimize : Utilize performance profiling tools such as React DevTools or Chrome DevTools to identify performance bottlenecks. Measure the rendering times, identify components with excessive re-renders, and analyze the performance of critical code sections. Optimize the identified areas by refactoring, simplifying computations, or implementing caching strategies.

By implementing these suggestions, you can improve the performance of your application, ensuring efficient rendering and minimizing unnecessary computations. Regular performance testing and profiling will help you identify specific areas for optimization and ensure your financial app performs optimally.

Here’s an updated version of the code with optimizations to address the performance concerns:

import React, { useMemo } from 'react';
const FinancialApp = ({ transactions }) => {
  // Pagination or virtualization logic here
  const getFormatAmount = useMemo(() => {
    const formatAmount = (amount) => `$${amount}`;
    return (type) => {
      switch (type) {
        case 'expense':
          return (amount) => `- $${amount}`;
        case 'income':
          return (amount) => `+ $${amount}`;
        case 'transfer':
          return (amount) => `$${amount}`;
        default:
          return formatAmount;
      }
    };
  }, []);
  return (
    <div>
      {/* Rendering logic with pagination or virtualization */}
      {visibleTransactions.map((transaction, index) => (
        <div key={transaction.id}>
          <TransactionItem transaction={transaction} formatAmount={getFormatAmount(transaction.type)} />
        </div>
      ))}
    </div>
  );
};
const TransactionItem = ({ transaction, formatAmount }) => {
  // Transaction item rendering logic here
  return (
    <div>
      <span>{transaction.description}</span>
      <span>{formatAmount(transaction.amount)}</span>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the updated code:

  1. The getFormatAmount function is memoized using the useMemo hook. This ensures that the function is only created once and doesn't get re-computed on subsequent renders unless the dependencies change. It improves performance by avoiding unnecessary function re-creation.
  2. The rendering logic includes placeholders for pagination or virtualization techniques. Based on your specific requirements, you can implement the necessary logic to render a subset of transactions based on the current viewport or user interactions. This approach improves performance by reducing the number of rendered items.
  3. The TransactionItem component receives the formatAmount function as a prop. This allows the component to access the appropriate format function without dynamically creating it on each render. It reduces redundancy and optimizes rendering by avoiding unnecessary function calls.

Remember to adapt the code to your specific needs, including the implementation of pagination or virtualization logic. These optimizations provide a starting point for improving performance, but the exact implementation will depend on your application’s requirements and complexity.

Here are some additional customization options for the Pagination component, each with its own code example:

  1. Custom Styling:
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
  const pages = [...Array(totalPages).keys()].map((page) => page + 1);

  return (
    <div className="pagination-container">
      {pages.map((page) => (
        <button
          key={page}
          onClick={() => onPageChange(page)}
          className={currentPage === page ? 'active' : ''}
        >
          {page}
        </button>
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In this example, we add a custom CSS class 'pagination-container' to the parent div element. You can then define the styling for the pagination component using CSS to match your desired look and feel.

2. Previous and Next Buttons:

const Pagination = ({ currentPage, totalPages, onPageChange }) => {
  const pages = [...Array(totalPages).keys()].map((page) => page + 1);

  return (
    <div>
      <button onClick={() => onPageChange(currentPage - 1)} disabled={currentPage === 1}>
        Previous
      </button>

      {pages.map((page) => (
        <button
          key={page}
          onClick={() => onPageChange(page)}
          className={currentPage === page ? 'active' : ''}
        >
          {page}
        </button>
      ))}

      <button onClick={() => onPageChange(currentPage + 1)} disabled={currentPage === totalPages}>
        Next
      </button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In this example, we add previous and next buttons to allow the user to navigate to the previous and next pages, respectively. The buttons are disabled when the user is on the first or last page to prevent unnecessary navigation.

3. Compact View:

const Pagination = ({ currentPage, totalPages, onPageChange }) => {
  const pages = [...Array(totalPages).keys()].map((page) => page + 1);

  return (
    <div>
      {pages.map((page) => (
        <button
          key={page}
          onClick={() => onPageChange(page)}
          className={currentPage === page ? 'active' : ''}
        >
          {page}
        </button>
      ))}

      <span>Page {currentPage} of {totalPages}</span>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In this example, we remove the previous and next buttons and instead display the current page number and total number of pages. This provides a more compact view of the pagination component.

4. Custom Labels:

const Pagination = ({ currentPage, totalPages, onPageChange }) => {
  const pages = [...Array(totalPages).keys()].map((page) => page + 1);

  return (
    <div>
      {pages.map((page) => (
        <button
          key={page}
          onClick={() => onPageChange(page)}
          className={currentPage === page ? 'active' : ''}
        >
          {page}
        </button>
      ))}

      <button onClick={() => onPageChange(currentPage + 1)} disabled={currentPage === totalPages}>
        Next Page
      </button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In this example, we customize the label of the next button to display “Next Page” instead of just “Next”. This provides a more descriptive and user-friendly label for navigation.

5. Page Size Selection:

const Pagination = ({ currentPage, totalPages, onPageChange, pageSizeOptions, pageSize, onPageSizeChange }) => {
  const pages = [...Array(totalPages).keys()].map((page) => page + 1);

  return (
    <div>
      {pages.map((page) => (
        <button
          key={page}
          onClick={() => onPageChange(page)}
          className={currentPage === page ? 'active' : ''}
        >
          {page}
        </button>
      ))}

      <select value={pageSize} onChange={(e) => onPageSizeChange(e.target.value)}>
        {pageSizeOptions.map((option) => (
          <option key={option} value={option}>
            {option}
          </option>
        ))}
      </select>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In this example, we introduce a page size selection feature. It includes a dropdown select element where the user can choose the number of items to display per page. The pageSizeOptions array contains the available page size options, and the pageSize state represents the currently selected page size. The onPageSizeChange function is called when the user selects a new page size.

6. Custom Page Range:

const Pagination = ({ currentPage, totalPages, onPageChange, pageRange }) => {
  const startPage = currentPage - Math.floor(pageRange / 2);
  const endPage = currentPage + Math.floor(pageRange / 2);
  const pages = [...Array(totalPages).keys()].map((page) => page + 1).slice(startPage, endPage + 1);

  return (
    <div>
      {pages.map((page) => (
        <button
          key={page}
          onClick={() => onPageChange(page)}
          className={currentPage === page ? 'active' : ''}
        >
          {page}
        </button>
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In this example, we introduce a custom page range feature. The pageRange represents the number of consecutive page buttons to display around the current page. The startPage and endPage variables calculate the range of pages to be shown based on the current page and the page range. The pages array is then sliced to obtain the desired range of page numbers.

Feel free to mix and match these customization options or come up with your own variations based on your application’s specific needs. The goal is to tailor the Pagination component to fit your desired functionality and design.

By leveraging the abstract method pattern in React, we can build a robust and modular financial app that effectively manages various types of transactions and ensures clean, reusable code.

Conclusion:

By implementing the Abstract Method pattern in React using a functional approach, we can achieve code reuse, remove redundancy, and avoid code smells. The abstract component provides a common structure and behavior, while child components extend it to define specific rendering and logic. This approach promotes a cleaner and more maintainable codebase, making it easier to scale and modify our applications.

Remember, the Abstract Method pattern is just one of many patterns that can be leveraged in React development. It’s important to evaluate its suitability based on the specific needs and complexity of your application. By applying design patterns thoughtfully, we can enhance code quality and build.

I Hope you liked it, By leveraging this pattern, you can build applications that are easier to manage and evolve over time. Understanding and applying design patterns in React empowers developers to write clean, efficient, and robust code.

Start incorporating the Abstract Design pattern into your React projects and experience the benefits firsthand. Happy coding!

Thanks for reading


Top comments (0)