paint-brush
Creating Effective And Optimized Next.JS Reusable Components.by@rockyessel
4,265 reads
4,265 reads

Creating Effective And Optimized Next.JS Reusable Components.

by Rocky EsselOctober 29th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

This guide focuses on creating reusable components in React for better code organization and maintenance. We've explored how to design versatile components like buttons, containers, and dropdowns, using ComponentProps and HTMLAttributes for adaptability. Proper prop handling ensures functionality and prevents errors. Mastering these techniques results in more organized and scalable React applications.

People Mentioned

Mention Thumbnail
featured image - Creating Effective And Optimized Next.JS Reusable Components.
Rocky Essel HackerNoon profile picture


Reusable components play a crucial role in the development process for several reasons. They are versatile building blocks that can be utilized throughout your application, offering benefits such as easier maintenance and bug fixing. When it comes to creating a reusable component for image or link elements, there are various approaches, but it's essential to explore more optimized and effective methods. Let's delve into this by discussing Next.js Image components and how they can be crafted in a more efficient way.

Old Way of Creating Reusable Next.js Image


import Image from 'next/image';


interface Props {
width: number,
height: number,
alt: string,
src: string,
styles: string // for styling
// other styling props
}

const NextImage = (props: Props) => {
  return (
    <Image
      src={props.src}
      width={props.width}
      height={props.height}
      alt={props.alt}
      className={props.styles}
    />
  );
};

export default NextImage;


In the traditional approach, the NextImage component is created with explicit prop definitions. While functional, it can become cumbersome when you need to modify this component for specific use cases. Making modifications could lead to the creation of multiple similar components, making your codebase less organized.

New Way, More Optimized:

'use client';
import Image from 'next/image';

import cn from 'clsx';
import { ComponentProps, useState } from 'react';

const NextImage = (props: ComponentProps<typeof Image>) => {
  const [isLoading, setLoading] = useState(true);

  return (
    <Image
      {...props}
      src={props.src}
      priority={true}
      className={cn(
        props.className,
        'duration-700 ease-in-out',
        isLoading ? 'scale-105 blur-lg' : 'scale-100 blur-0'
      )}
      onLoadingComplete={() => setLoading(false)}
    />
  );
};

export default NextImage;


The new approach offers a more optimized and efficient way to create a reusable Next.js Image component. It utilizes the ComponentProps type, enabling the component to inherit and adapt to a broader range of props. This versatility allows you to make specific modifications as needed without cluttering your codebase with additional components. In addition, you take advantage of Next.js Image component features such as priority, which optimizes image loading.


To further clarify, the clsx utility library is used to conditionally apply CSS classes to elements, enhancing the component's styling capabilities. The useState hook manages the loading state of the image, and you've set up basic styling for the component, including the ability to scale and blur the image based on loading conditions. By adopting this approach, you maintain the flexibility to tailor your Next.js image component while keeping your codebase organized and efficient.


Link Components

The traditional way of rendering Next.Js links can be less effective and makes modification difficult, as we’ve seen above, so let me code you the code for a more optimized Link Component:


import Link from 'next/link';
import cn from 'clsx';
import { ComponentProps } from 'react';

const NextLink = (props: ComponentProps<typeof Link>) => {
  return (
    <Link
      {...props}
      className={cn(
        props.className,
        `hover:underline font-medium text-black transition-all duration-200 rounded dark:text-gray-300`
      )}
      href={props.href}
      title={props.title}
    >
      {props.children}
    </Link>
  );
};

export default NextLink;


Much like the Image Component we discussed earlier, this component comes with basic styling for consistency and usability. However, it offers a more optimized approach to rendering links in your Next.js application. This ensures that your links not only maintain a consistent style but also remain adaptable and easy to modify as your project evolves.


Bonus

In our pursuit of creating reusable components, it's essential to extend this approach to various other elements like buttons, headers (h1), paragraphs (p), code blocks, or even custom container elements. While we won't be using ComponentProps this time, we can still use the element's interface. Let's explore this by creating a custom button component:


Button Components

import cn from "clsx";
import { ButtonHTMLAttributes } from "react";

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  className?: string;
}

const Button = (props: ButtonProps) => {
  const { className, ...rest } = props;
  return (
    <button {...rest} className={cn(className)}>
      {props.children}
    </button>
  );
};

export default Button;


As you can see, we've created an interface called ButtonProps, extending it to inherit all the props from the button attributes. We've also considered the possibility that the className can be undefined or have custom styling provided by the user. This allows us to create a custom button component that's both versatile and adaptable, ensuring a consistent structure for your buttons while allowing for easy modifications.


Custom Element (Containers)

When creating web pages, maintaining a structured layout is crucial. To achieve this, you might need to design containers for your content. Similar to what we discussed with the Button Component, we can use a similar approach to create these containers. Let's explore how to create a basic container for your web pages that can be used consistently throughout your application.


Here's an example of a simple yet versatile container:

import cn from "clsx";
import { HTMLAttributes } from "react";

interface SectionProps extends HTMLAttributes<HTMLElement> {
  className?: string;
}
const Container = (props: SectionProps) => {
  return (
    <section
      {...props}
      className={cn(
        props.className,
        "px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl bg-white mt-8"
      )}
    >
      {props.children}
    </section>
  );
};

export default Container;


This Container component, like the previous Button component, doesn't introduce custom props or additional complexities. It simply relies on the className for styling, making it adaptable and straightforward for use across various parts of your application.


A little Complex Resuable Component

Now, let's consider a more complex scenario where we want to create a dropdown menu or list. In such cases, we need to introduce custom props to handle the menu's visibility. We'll define the showUserDropdown and setShowUserDropdown props to manage this.


Here's the initial version of the component:

import cn from "clsx";
import { useEffect, useRef } from "react";

interface DropdownWrapperProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode;
  showUserDropdown: boolean;
  setShowUserDropdown: React.Dispatch<React.SetStateAction<boolean>>;
}

const DropdownWrapper = (props: DropdownWrapperProps) => {
  const userDropdownRef = useRef<HTMLDivElement | null>(null);
  const { className, ...rest } = props;

  useEffect(() => {
    const handleOutsideClick = (event: any) => {
      if (
        props.showUserDropdown &&
        userDropdownRef.current &&
        !userDropdownRef.current.contains(event.target)
      ) {
        props.setShowUserDropdown(false);
      }
    };
    document.addEventListener("click", handleOutsideClick);
    return () => {
      document.removeEventListener("click", handleOutsideClick);
    };
  }, [props]);
  return (
    <div {...rest} className={cn(className)} ref={userDropdownRef}>
      {props.children}
    </div>
  );
};

export default DropdownWrapper;


This component is functional but not entirely correct; why? Because we are passing the showUserDropdown and setShowUserDropdown to the div as {..rest} which will either throw an error in the server(terminal) or client(UI). So to fix this, we destruct it alongside className, then modify props.showUserDropdown to showUserDropdown and the same for setShowUserDropdown.


import cn from "clsx";
import { useEffect, useRef } from "react";

interface DropdownWrapperProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode;
  showUserDropdown: boolean;
  setShowUserDropdown: React.Dispatch<React.SetStateAction<boolean>>;
}

const DropdownWrapper = (props: DropdownWrapperProps) => {
  const userDropdownRef = useRef<HTMLDivElement | null>(null);
  const { className, showUserDropdown, setShowUserDropdown, ...rest } = props;

  useEffect(() => {
    const handleOutsideClick = (event: any) => {
      if (
        showUserDropdown &&
        userDropdownRef.current &&
        !userDropdownRef.current.contains(event.target)
      ) {
        setShowUserDropdown(false);
      }
    };
    document.addEventListener("click", handleOutsideClick);
    return () => {
      document.removeEventListener("click", handleOutsideClick);
    };
  }, [props]);
  return (
    <div {...rest} className={cn(className)} ref={userDropdownRef}>
      {props.children}
    </div>
  );
};

export default DropdownWrapper;


By making this adjustment, the component functions correctly, ensuring the showUserDropdown and setShowUserDropdown props are used in a way that won't cause issues in either the server or the client-side rendering.


Conclusion

In this exploration of creating reusable components and handling more complex elements in React, we've seen how to enhance the efficiency and maintainability of your code. By designing components like buttons, containers, and dropdowns, we've shown how you can structure your application for consistency while keeping the flexibility to adapt and modify as needed.


What I’m doing: I’m currently building element components for more reusability and accessibility. If you’re interested, then please sign up for my newsletter here to be the first to receive a Github link(it’s free).

Profile Image. Making sure you don't get lost 😂