Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 70

Building a Reusable Layout Component

Recommended resources

Hello! Let's get started on the next part of our e-commerce project.

In our last lesson, we set up the basic navigation for our application using React Router. We created a persistent Navbar that appears on every page by placing it outside the <Routes> container. While this works, a more structured and scalable approach is to use a dedicated Layout component.

Today, we will create a reusable Layout component that includes a shared header and footer. This will involve refactoring our routing setup to use one of React Router's most powerful features for clean architecture: nested routes and the <Outlet> component.

Why Use a Layout Component?

In any web application, certain elements like the header, footer, or a sidebar are present on most, if not all, pages. A Layout component acts as a template or "shell" for our pages, defining this shared structure in one place. The page-specific content is then rendered inside this shell.

This approach keeps our code DRY (Don't Repeat Yourself) and makes it much easier to manage the overall structure of the app.

Think of it like a component tree. A main page is composed of smaller, reusable blocks.

React Component Tree Diagram
This diagram illustrates how a page can be composed of shared components like a `Header` and `Footer`, with page-specific content (like an `Article`) rendered in between. This is the exact structure we are aiming for.

Step 1: Creating the Header and Footer Components

First, let's create the two main pieces of our layout. In the previous lesson, we made a Navbar. We can rename this to Header and also create a brand new Footer component.

For inspiration, you can look at a real-world e-commerce project. The following article provides code for Header and Footer components. Don't worry about the complex state management (useCartContext, token, etc.) in the examples for now; we'll add that functionality later. Focus on the JSX structure and styling.

Build a Full-Stack E-Commerce React App from Scratch

Take a look at the 'Build a Full-Stack E-Commerce React App from Scratch' article on Dev.to. It provides excellent examples of common e-commerce components.

Scan through the sections '1. Header across the app' and '2. Footer across the app'. Pay attention to the JSX structure and how Link is used for navigation in the header. We will create simplified versions of these.

Now, let's create our own versions.

  1. Create the Header:
    If you have a src/components/Navbar.jsx, rename it to Header.jsx. Replace its content with the following, which is a simplified version of the navigation we built last time.

    // src/components/Header.jsx
    import { Link } from 'react-router-dom';
    
    export default function Header() {
      // Basic inline styles for clarity
      const headerStyle = {
        background: '#2d3748', // A dark gray
        padding: '1rem',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
      };
    
      const linkStyle = {
        color: 'white',
        margin: '0 1rem',
        textDecoration: 'none',
      };
    
      return (
        <header style={headerStyle}>
          <Link to="/" style={{...linkStyle, fontSize: '1.5rem'}}>
            MyShop
          </Link>
          <nav>
            <Link to="/" style={linkStyle}>Home</Link>
            <Link to="/products" style={linkStyle}>Products</Link>
            <Link to="/cart" style={linkStyle}>Cart</Link>
            <Link to="/login" style={linkStyle}>Login</Link>
          </nav>
        </header>
      );
    }
    
  2. Create the Footer:
    Create a new file src/components/Footer.jsx.

    // src/components/Footer.jsx
    export default function Footer() {
      const footerStyle = {
        background: '#1a202c', // A very dark gray
        color: '#a0aec0', // A light gray text
        padding: '1.5rem',
        textAlign: 'center',
        marginTop: 'auto', // Pushes footer to the bottom in a flex container
      };
    
      return (
        <footer style={footerStyle}>
          <p>&copy; {new Date().getFullYear()} MyShop. All rights reserved.</p>
        </footer>
      );
    }
    

Step 2: The <Outlet> for Shared Layouts

Now that we have the pieces, how do we tell React Router to render our page content between the header and footer? The answer is nested routes and the <Outlet> component.

When you nest <Route> components, the parent Route can define a layout element. This layout component then uses <Outlet> as a placeholder to render the matched child route's element.

Let's see how this works.

Ultimate React Router v6 Guide

This article, 'Ultimate React Router v6 Guide', explains the shared layout pattern very clearly.

Read the subsection titled 'Shared Layouts'. It shows exactly how to pass an element prop to a parent route and how the Outlet component works as a placeholder. This is the core concept for this lesson.


Step 3: Creating the Layout Component

Based on what you just read, let's create our Layout.jsx file. This component will import our Header, Footer, and the Outlet from react-router-dom.

Create a new file src/components/Layout.jsx.

// src/components/Layout.jsx
import { Outlet } from 'react-router-dom';
import Header from './Header';
import Footer from './Footer';

export default function Layout() {
  const layoutStyle = {
    display: 'flex',
    flexDirection: 'column',
    minHeight: '100vh', // Ensures the layout takes at least the full viewport height
  };

  const mainStyle = {
    flex: 1, // Allows the main content area to grow and fill available space
    padding: '1rem',
  };

  return (
    <div style={layoutStyle}>
      <Header />
      <main style={mainStyle}>
        <Outlet /> {/* Child routes will be rendered here */}
      </main>
      <Footer />
    </div>
  );
}

Notice the use of <Outlet />. This is where React Router will swap in HomePage, ProductsPage, etc., depending on the URL.


Step 4: Refactoring Your Routes in App.jsx

The final step is to update App.jsx to use our new Layout component and nested route structure. We will no longer render the Header (previously Navbar) directly in App.jsx.

Replace the content of your App.jsx with the following:

// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout'; // Import the Layout component
import HomePage from './pages/HomePage';
import ProductsPage from './pages/ProductsPage';
import CartPage from './pages/CartPage';
import LoginPage from './pages/LoginPage';

function App() {
  return (
    <Routes>
      {/* Parent Route with the Layout component */}
      <Route path="/" element={<Layout />}>
        {/* Child Routes that will be rendered inside the Outlet */}
        <Route index element={<HomePage />} />
        <Route path="products" element={<ProductsPage />} />
        <Route path="cart" element={<CartPage />} />
        <Route path="login" element={<LoginPage />} />
        {/* We can add a "Not Found" page here later */}
      </Route>
    </Routes>
  );
}

export default App;

Key Changes:

  • We have a single parent <Route> that uses / as its path and renders our Layout component.
  • The page routes are now nested inside this parent Route. Their paths are now relative to the parent (e.g., path="products" instead of path="/products").
  • The route for the homepage uses the index prop, which tells the router to render this component when the URL matches the parent's path exactly (/).

Run your application now. The structure should look the same, but your code is much cleaner and more maintainable. The header and footer are now part of a single, reusable Layout component.


Conclusion

Excellent work! You have successfully refactored the application to use a professional layout pattern. This structure is highly scalable and will serve as a solid foundation for the rest of our e-commerce app.

Key Takeaways:

  • A Layout component is a reusable shell for pages, containing shared UI elements like a header and footer.
  • React Router's nested routes allow you to apply a layout to a group of pages.
  • The <Outlet> component acts as a placeholder within a layout, rendering the content of the matched child route.
  • This pattern keeps your App.jsx clean and centralizes your app's structural logic in the Layout component.

Preview of the Next Lesson:

Now that our application's skeleton is robust, it's time to bring it to life with data. In the next lesson, we will build a product listing page that fetches product data from an API and handles loading and error states. This will be our first step in creating a dynamic, data-driven catalog.