Common Coding Practices

In this article we will go over some common design patterns that I find myself using a lot and how to implement them in a React project.


Callout

Callouts are effective tools for capturing a user's attention. They can be utilized to convey important information, warnings, or simply to engage the user's interest. I use callouts in my blog post to highlight key points.

Default

Warning

Danger

import { cn } from "@/lib/utils";
import { ReactNode } from "react";
 
interface CalloutProps {
	children?: ReactNode;
	type?: "default" | "warning" | "danger";
}
 
export function Callout({
	children,
	type = "default",
	...props
}: CalloutProps) {
	return (
		<div
			className={cn(
				"my-6 items-start rounded-md border border-l-8 p-4 w-full dark:max-w-none",
				{
					"border-red-900 bg-red-50 dark:prose": type === "danger",
					"border-yellow-600 bg-yellow-50 dark:prose": type === "warning",
				}
			)}
			{...props}>
			<div>{children}</div>
		</div>
	);
}
 

Above we see the cn function being used to merge default styles with the styles passed as props. Allowing for easy overriding of styles.

Questions that arose when implementing this component:

  • Why is the '...props' necessary? The '...props' is necessary to pass any additional props to the component. This is useful if you want to add additional styling to the component. It allows for any additional props to be passed to the component, in a way future-proofing the component.

Inspired by airbnb, I used this pattern when developing the memory mapper project. Each card represents a memory location and contains a carousel of photos associated with that memory location. I am going to use shadcn/ui for the carousel and the card.

import Image from "next/image"
import { Card, CardContent } from "@/components/ui/card"
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"
 
interface MemoryCardProps {
  title: string
  date: string
  photos?: string[]
}
 
export default function MemoryCard({ title, date, photos = [] }: MemoryCardProps) {
  return (
    <Card className="w-full max-w-md mx-auto overflow-hidden">
      <CardContent className="p-0">
        <div className="relative aspect-video">
          {photos && photos.length > 0 ? (
            <Carousel>
              <CarouselContent>
                {photos.map((photo, index) => (
                  <CarouselItem key={index}>
                    <div className="relative aspect-video">
                      <Image
                        src={photo}
                        alt={`Memory photo ${index + 1}`}
                        fill
                        className="object-cover"
                        onError={(e) => {
                          const target = e.target as HTMLImageElement;
                          target.src = "/placeholder.svg?height=400&width=600";
                          target.alt = "Error loading image";
                        }}
                      />
                    </div>
                  </CarouselItem>
                ))}
              </CarouselContent>
              <CarouselPrevious className="absolute left-2 top-1/2 -translate-y-1/2" aria-label="Previous slide" />
              <CarouselNext className="absolute right-2 top-1/2 -translate-y-1/2" aria-label="Next slide" />
            </Carousel>
          ) : (
            <div className="flex items-center justify-center h-full bg-gray-200">
              <p className="text-gray-500">No photos available</p>
            </div>
          )}
        </div>
        <div className="p-4">
          <h2 className="text-2xl font-bold mb-2">{title}</h2>
          <p className="text-sm text-gray-500">{date}</p>
        </div>
      </CardContent>
    </Card>
  )
}