english-everyday
== write & share ==

Programming Design Patterns in React and Next.js

September 21, 2025

Programming Design Patterns in React and Next.js


Design patterns are essential tools for building maintainable, scalable, and clean code. In this comprehensive guide, we’ll explore the most commonly used design patterns in React and Next.js applications, with practical examples from real-world ecommerce projects.

Table of Contents


Design Patterns in React

What design patterns do you often use in React and why are they suitable for ecommerce?

Answer:

In React, I often use these design patterns because they work well with ecommerce apps:

  • Module Pattern: Split logic into modules (like utils/cart.js or custom hooks) for reuse, example: useCart to manage shopping cart.

  • Higher-Order Component (HOC): Used to share logic, like withAuth to check login before accessing checkout page.

  • Render Props: Used for dynamic components, example: ProductList allows passing render function to customize product display.

  • Compound Components: Create tightly linked components, like Tabs and Tab to show product info (description, reviews).

  • Hooks Pattern: Reuse logic through custom hooks, example: useProductFetch to fetch and cache product data.

Why suitable for ecommerce: Ecommerce has many repeated features (cart, product lists) and needs flexible UI. These patterns help make code modular, easy to maintain, and support expansion when adding features like multi-platform payments.


Higher-Order Component (HOC)

Explain Higher-Order Component (HOC) and when should you use it in React?

Answer:

HOC is a function that takes a component and returns a new component with extra logic. It helps reuse logic without changing the original component, following the Open-Closed principle.

How it works: HOC wraps a component, adds props or handles logic (like checking auth, fetching data). Example:

// withAuth.js
import React from "react";
import { useRouter } from "next/router";

const withAuth = (WrappedComponent) => {
  return (props) => {
    const router = useRouter();
    const isAuthenticated = checkAuth(); // Mock function to check login

    if (!isAuthenticated) {
      router.push("/login");
      return null;
    }

    return <WrappedComponent {...props} />;
  };
};

export default withAuth;

// Usage: const ProtectedCheckout = withAuth(CheckoutPage);

When to use in ecommerce: Use HOC when you need to apply common logic to many components, like:

  • Check login before entering checkout page (CheckoutPage).
  • Add analytics tracking for components like ProductCard or AddToCartButton.
  • Manage loading state when fetching product data.

Note: With modern React, custom hooks often replace HOC in many cases because they’re simpler, but HOC is still useful when you need to wrap entire components.


Compound Components Pattern

Have you used Compound Components pattern in any project? Please give a specific example

Answer:

Compound Components is a pattern that allows child components to share hidden state with parent component, creating flexible API. I used this pattern in an ecommerce project to build tabbed interface for product detail page.

Example: Create ProductTabs component to show “Description”, “Reviews”, and “Specifications” tabs.

// ProductTabs.js
import React, { useState } from "react";

const ProductTabs = ({ children }) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <div>
      <div className="tabs">
        {React.Children.map(children, (child, index) => (
          <button
            key={index}
            onClick={() => setActiveTab(index)}
            style={{ fontWeight: activeTab === index ? "bold" : "normal" }}
          >
            {child.props.label}
          </button>
        ))}
      </div>
      <div>{children[activeTab]}</div>
    </div>
  );
};

const Tab = ({ children }) => {
  return <div>{children}</div>;
};

ProductTabs.Tab = Tab;

export default ProductTabs;

// Usage:
// <ProductTabs>
//   <ProductTabs.Tab label="Description">Product description content</ProductTabs.Tab>
//   <ProductTabs.Tab label="Reviews">Reviews list</ProductTabs.Tab>
// </ProductTabs>

Application in ecommerce: This pattern helps show product info flexibly, easy to expand when adding new tabs (like “Q&A”) without changing parent component.


Observer Pattern in State Management

How to apply Observer pattern in state management of an ecommerce app?

Answer:

Observer Pattern allows components to listen and react to state changes. In React, I apply this pattern through Context API or libraries like Redux to manage global state, example: cart state.

How to apply:

  • Create CartContext to store cart state (products, quantity, total).
  • Components like CartIcon, CartPage subscribe to receive updates when cart changes.
  • When user adds/edits products, CartContext notifies to make components re-render.

Example: In an ecommerce project, I used Context API to sync cart:

// CartContext.js
import React, { createContext, useState } from "react";

export const CartContext = createContext();

export const CartProvider = ({ children }) => {
  const [cart, setCart] = useState([]);

  const addToCart = (product) => {
    setCart((prev) => [...prev, product]); // Notify subscribers
  };

  return (
    <CartContext.Provider value={{ cart, addToCart }}>
      {children}
    </CartContext.Provider>
  );
};

// Usage: Wrap app in <CartProvider>, child components use useContext(CartContext)

Why suitable: Cart is global state, needs sync between many components (header, checkout page). Observer pattern helps manage efficiently, avoid code duplication.


Hooks Pattern for Reusable Logic

In an ecommerce app, what pattern would you use to manage reusable logic between components (example: cart handling logic)?

Answer:

I use Hooks Pattern to manage reusable logic, because it’s simple, easy to integrate, and fits modern React. For example, to handle cart logic (add, remove, calculate total), I create useCart hook:

// useCart.js
import { useState, useCallback } from "react";

const useCart = () => {
  const [cart, setCart] = useState([]);

  const addToCart = useCallback((product) => {
    setCart((prev) => [...prev, { ...product, quantity: 1 }]);
  }, []);

  const removeFromCart = useCallback((productId) => {
    setCart((prev) => prev.filter((item) => item.id !== productId));
  }, []);

  const getTotal = useCallback(() => {
    return cart.reduce((total, item) => total + item.price * item.quantity, 0);
  }, [cart]);

  return { cart, addToCart, removeFromCart, getTotal };
};

export default useCart;

// Usage: const { cart, addToCart } = useCart();

Why suitable: Hooks allow reusing cart logic in many components (like AddToCartButton, CartSummary) without code duplication. It’s also easy to test and maintain.


Module Pattern vs Hooks

Compare advantages/disadvantages of Module pattern and Hooks in organizing React code

Answer:

  • Module Pattern:

    • Advantages: Split logic into separate files (like utils/cart.js), easy to use in non-React projects, supports tree-shaking to optimize bundle size.
    • Disadvantages: Not tightly integrated with React lifecycle, must pass state manually, can lead to code duplication when used in many components.
    • Ecommerce example: Use Module pattern for formatPrice function in utils/price.js, called in many places.
  • Hooks Pattern:

    • Advantages: Directly integrated with React (state, effect), easy to share logic between components, supports memoization (useCallback). Example: useCart hook handles cart and automatically re-renders when state changes.
    • Disadvantages: Only works in React, cannot reuse outside React.
    • Ecommerce example: Use useCart to manage cart, automatically sync UI.

Conclusion: Hooks are better for state/UI related logic in React (like cart), while Module pattern is good for pure logic (like data formatting). In ecommerce, I prefer Hooks for dynamic features (cart, filters) and Module for static logic (tax calculation).


React vs Pure JavaScript

Why is React preferred over pure JavaScript for building ecommerce interfaces?

Answer:

React is preferred over pure JavaScript because:

  • Component-based architecture: Reuse components (like ProductCard, CartItem) helps build complex UI (product lists, cart) faster than manual DOM manipulation.

  • Virtual DOM: Reduces reflow/repaint, improves performance when updating dynamic UI (like updating cart quantity).

  • State management: useState, useReducer help manage state (product filtering, cart) easier than managing global variables in pure JS.

  • Ecosystem: Supports libraries like React Router, React Query, suitable for ecommerce (routing, caching).

  • Developer experience: JSX, hot reloading, and tools like ESLint improve development speed.

Ecommerce example: With React, I build reusable ProductList component, automatically re-renders when filter changes, faster than writing event listeners in pure JS.


Virtual DOM and Performance

How does Virtual DOM in React work, and how does it improve performance?

Answer:

Virtual DOM is a lightweight copy of real DOM, used by React to optimize UI updates:

  • How it works: When state changes, React creates new Virtual DOM, compares (diffing) with old version to find minimal changes, then updates real DOM only where needed.

  • Performance improvement: Reduces direct DOM operations (causing expensive reflow/repaint). For example, when adding product to cart, only <span> showing quantity gets updated, doesn’t re-render entire page.

  • Ecommerce example: In product list, when user filters by price, Virtual DOM only updates display list, keeps header/footer unchanged, helps reduce render time.


React Hooks Comparison

Explain the differences between useState, useReducer, and useContext in React

Answer:

  • useState: Manages simple state in one component. Use when state is small, logic is simple, example: store open/close state of modal in product page.

  • useReducer: Manages complex state with many actions. Use for complex logic, like handling cart (add, remove, update quantity).

  • useContext: Share global state/logic between many components without prop drilling. Use for common data like cart, user info.

Ecommerce examples:

  • useState: Store open/close state of product filter.
  • useReducer: Manage cart (add, remove, update quantity).
  • useContext: Share cart for CartIcon (header) and CartPage.

Custom Hooks in Practice

Have you used custom hooks in any project? Please describe a custom hook you wrote

Answer:

I wrote useProductFetch in an ecommerce project to fetch and cache product data from API:

// useProductFetch.js
import { useState, useEffect } from "react";

const useProductFetch = (productId) => {
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchProduct = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/products/${productId}`);
        const data = await response.json();
        setProduct(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchProduct();
  }, [productId]);

  return { product, loading, error };
};

export default useProductFetch;

// Usage: const { product, loading, error } = useProductFetch('123');

Application: This hook is used in ProductDetail and RelatedProducts to fetch data, reduces code duplication and easily add caching (with React Query).


Next.js Data Fetching

In Next.js, when would you choose getStaticProps over getServerSideProps? Give ecommerce related examples

Answer:

  • getStaticProps: Creates static content at build time, suitable for data that doesn’t change often (like product pages, categories). Supports SEO and fast loading.

  • getServerSideProps: Creates dynamic content at request time, suitable for data that changes constantly (like cart, user info).

Ecommerce examples:

  • getStaticProps: Use for product list pages (/products/[category]) because product categories change rarely, supports SEO and caching.

  • getServerSideProps: Use for cart page (/cart) because cart data changes per user.

Why choose: getStaticProps reduces server load and improves performance for product pages, while getServerSideProps ensures real-time data for cart.


Dynamic Routing in Next.js

How to handle dynamic routing in Next.js for a product category page?

Answer:

In Next.js, dynamic routing is handled through [param] folder structure:

  • Create [category].js file in pages/products/ to handle URLs like /products/electronics.
  • Use getStaticPaths to create list of static paths (if using SSG) and getStaticProps to fetch category data.
// [category].js
import { useRouter } from "next/router";

export async function getStaticPaths() {
  const categories = ["electronics", "clothing"]; // Mock category list
  const paths = categories.map((category) => ({
    params: { category },
  }));
  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  const response = await fetch(`/api/products?category=${params.category}`);
  const products = await response.json();
  return { props: { products } };
}

const CategoryPage = ({ products }) => {
  const router = useRouter();
  const { category } = router.query;

  return (
    <div>
      <h1>Category: {category}</h1>
      {products.map((product) => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
};

export default CategoryPage;

Application: Ensures SEO-friendly URLs (/products/electronics) and supports caching for performance.


Third-party API Integration

Explain how you integrate third-party APIs (like Stripe for payments) in a Next.js app

Answer:

To integrate Stripe in Next.js:

  1. Install: Install @stripe/stripe-js and stripe (server-side).
  2. Client-side: Use loadStripe to initialize Stripe and render payment form.
  3. Server-side: Create API route (/api/create-payment-intent) to call Stripe API, create PaymentIntent.
  4. Handle payment: Use Stripe Elements to collect card info and confirm payment.
// CheckoutPage.js
import { loadStripe } from "@stripe/stripe-js";
import {
  Elements,
  CardElement,
  useStripe,
  useElements,
} from "@stripe/react-stripe-js";

const stripePromise = loadStripe("pk_test_...");

const CheckoutForm = () => {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (e) => {
    e.preventDefault();
    const { error, paymentIntent } = await stripe.confirmCardPayment(
      await (
        await fetch("/api/create-payment-intent", { method: "POST" })
      ).json().clientSecret,
      { payment_method: { card: elements.getElement(CardElement) } }
    );
    if (error) console.error(error);
    else console.log("Payment successful:", paymentIntent);
  };

  return (
    <form onSubmit={handleSubmit}>
      <CardElement />
      <button type="submit">Pay</button>
    </form>
  );
};

const CheckoutPage = () => (
  <Elements stripe={stripePromise}>
    <CheckoutForm />
  </Elements>
);

export default CheckoutPage;
// create-payment-intent.js
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export default async function handler(req, res) {
  if (req.method === "POST") {
    try {
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 1000, // Mock amount
        currency: "usd",
      });
      res.status(200).json({ clientSecret: paymentIntent.client_secret });
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  }
}

Application: Ensures secure payment, smooth integration with ecommerce UI.


Project Structure Organization

How would you organize folder structure for a large ecommerce project with Next.js?

Answer:

I organize folder structure by features to make it easy to expand and maintain:

/components
  /cart
    CartItem.js
    CartSummary.js
  /product
    ProductCard.js
    ProductList.js
/hooks
  useCart.js
  useProductFetch.js
/pages
  /products
    [category].js
    [id].js
  /cart
    index.js
/api
  products.js
  create-payment-intent.js
/utils
  price.js
  api.js
/styles
  globals.css
  tailwind.config.js

Reasons:

  • By features: Group code by modules (cart, product) to make it easy to find and expand.
  • Separate logic: Hooks and utils separated for reuse.
  • API routes: Place in /api to manage server-side logic (like Stripe).

Error Handling in Next.js

How to handle errors in data fetching of Next.js?

Answer:

To handle errors in data fetching:

  • Client-side: Use state to track errors and show appropriate UI (like error messages).
  • Server-side: In getStaticProps/getServerSideProps, return error prop or redirect.
  • API routes: Return status code and error message.
// ProductPage.js
export async function getServerSideProps({ params }) {
  try {
    const response = await fetch(`/api/products/${params.id}`);
    if (!response.ok) throw new Error("Product not found");
    const product = await response.json();
    return { props: { product } };
  } catch (error) {
    return { props: { error: error.message } };
  }
}

const ProductPage = ({ product, error }) => {
  if (error) return <div>Error: {error}</div>;
  return <div>{product.name}</div>;
};

export default ProductPage;

Application: Ensures smooth user experience, shows friendly error messages when API fails.


Conclusion

Understanding and applying these design patterns in React and Next.js is crucial for building scalable, maintainable ecommerce applications. Each pattern serves a specific purpose:

  • HOC for cross-cutting concerns like authentication
  • Compound Components for flexible, composable UI
  • Observer Pattern for global state management
  • Hooks Pattern for reusable logic
  • Module Pattern for pure utility functions

The key is to choose the right pattern for the right situation, keeping in mind the specific requirements of your ecommerce project. Start with simple patterns and gradually adopt more complex ones as your application grows.

Remember: the goal is not to use every pattern, but to use the right patterns that make your code more maintainable, testable, and scalable.


Profile picture

written by english-everyday
Eat, sleep, WRITE, Judo repeat... Twitter