feelixe.
chevron right icon Back

My React Component Style Guide

2025-07-16 10:02
Felix Eklöf

There are many ways to write a React component, and no clear standard has been established within the community. Ideally, we’d all follow a consistent format so components look and feel the same.

Until then, here’s what I think is key to writing readable and maintainable React components.

Don’t use class components

It’s now the consensus: the React community has overwhelmingly embraced functional components as the preferred approach, and the official documentation explicitly recommends writing components as functions.

Functional components are better because:


Use a named, exported Props type

It is my preference to declare the components props before the component as a named and exported type. Always with the name [component-name]Props.

export type CounterProps = { count: number };

export function Counter(props: CounterProps)

Rather than:

export function Counter(props: { count: number })

This is mostly my own personal preference but it has some benefits.


Don’t destructure in parameters

When destructuring props inside the parameters, you often end up with messy looking code. Combine this with declaring props inside the parameters and you got yourself a cluttered mess.

Don’t do this 👎:

export function Counter({
  count,
  max,
  min,
  title,
}: { count: number; max: number; min: number; title: string }) {}

We often overuse destructuring. Simply reading the value from props.count is often a good option, as it makes it clear you’re accessing a prop and where it comes from. However, in React we sometimes need to destructure to exclude props we don’t want to pass to a child. In those cases, I recommend destructuring inside the function instead.

Do this 👍:

export type CounterProps = {
  count: number;
  max: number;
  min: number;
  title: string;
};

export function Counter(props: CounterProps) {
  const { count, max, min, title } = props;
}

Don’t use arrow functions

This point is mainly about code aesthetics. I think arrow functions are overused in the JavaScript community. Historically, lambdas were meant for short, single-line functions—not 100-line React components. Personally, I also think it just looks cleaner to use a regular function. Since arrow functions don’t make the code any shorter in this case, I don’t see any reason to use them for React components.

Don’t do this 👎:

export const Counter = (props: CounterProps) => {
  return <div />;
};

Do this 👍:

export function Counter(props: CounterProps) {
  return <div />;
}

Forward props to first child

It is usually a good practice to include the first child’s props in your component’s props. Like this:

import type { ComponentProps } from "react";

export type CounterProps = ComponentProps<"div"> & {
  count: number;
};

export function Counter(props: CounterProps) {
  const { count, children, ...rest } = props;

  return <div {...rest}>{children}</div>;
}

I think this is a good idea because you’re creating a more reusable component, and less need for wrapping it in useless divs.


Merge Props

Another good practice is to merge props wherever possible—especially for event handlers, style, and className.

Here’s an example of a component that merges all three.

import { useState, type ComponentProps, type MouseEventHandler } from "react";
import { cn } from "@/lib/utils";

export type CounterProps = ComponentProps<"button">;

export function Counter(props: CounterProps) {
  const { children, className, style, onClick, ...rest } = props;

  const [count, setCount] = useState(0);

  const clickHandler: MouseEventHandler<HTMLButtonElement> = (e) => {
    onClick?.(e);
    if (e.isDefaultPrevented()) {
      return;
    }
    setCount((prev) => prev + 1);
  };

  return (
    <button
      type="button"
      className={cn("bg-red", className)}
      style={{
        cursor: "pointer",
        ...style,
      }}
      onClick={clickHandler}
      {...rest}
    >
      {children}
    </button>
  );
}

Composable if applicable

Making your component composable is sometimes a good idea. However, often it makes more sense to keep it as a single component, especially for smaller components. Use this approach carefully and weigh the pros and cons. A composable component can be less obvious to someone unfamiliar with how it’s meant to be used.

Here’s an example of what a composable component looks like:

import type { ComponentProps } from "react";
import { cn } from "@/lib/utils.ts";

export type ArticleProps = ComponentProps<"article">;

export function Article(props: ArticleProps) {
  const { className, children, ...rest } = props;

  return (
    <article className={cn("bg-slate-300 grid gap-2", className)} {...rest}>
      {children}
    </article>
  );
}

export type ArticleTitleProps = ComponentProps<"h2">;

export function ArticleTitle(props: ArticleTitleProps) {
  const { className, children, ...rest } = props;

  return (
    <h2 className={cn("text-xl font-medium", className)} {...rest}>
      {children}
    </h2>
  );
}

export type ArticleDescriptionProps = ComponentProps<"p">;

export function ArticleDescription(props: ArticleDescriptionProps) {
  const { className, children, ...rest } = props;

  return (
    <p className={cn("tracking-tight text-slate-700", className)} {...rest}>
      {children}
    </p>
  );
}

That can be used like this:

export function Page() {
  return (
    <Article>
      <ArticleTitle>My first article</ArticleTitle>
      <ArticleDescription>This is a description</ArticleDescription>
    </Article>
  );
}

Don’t make components too big

This one is tricky—probably the hardest to get right. On one hand, I think it’s unhelpful to blindly follow a rule about keeping components under a certain number of lines. On the other hand, writing extremely long components can make your code hard to read. The key is to stay flexible rather than enforcing a strict limit. When it makes sense, break your component into smaller pieces. But be careful: splitting it the wrong way can actually make it even harder to follow.