skip to content
logo
Table of Contents

Writing Scalable React Components

Reusable, well-structured components improve maintainability, scalability, and performance in React applications.
This guide covers:

  • Component composition over inheritance
  • Controlled vs uncontrolled components
  • Performance optimizations
  • Accessibility best practices

1. Component Composition Over Inheritance

React promotes composition instead of traditional object-oriented inheritance.

Bad: Inheritance-Based Approach

class BaseButton extends React.Component<{ label: string }> {
render() {
return <button>{this.props.label}</button>;
}
}
class PrimaryButton extends BaseButton {}

Good: Composition-Based Approach

const Button = ({ children }: { children: React.ReactNode }) => {
return <button>{children}</button>;
};
const App = () => <Button>Click Me</Button>;

Composition makes components flexible and reusable.


2. Controlled vs Uncontrolled Components

Controlled components are fully managed by React state, while uncontrolled components use direct DOM access.

Controlled Input

import React, { useState } from "react";
const ControlledInput = () => {
const [value, setValue] = useState("");
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
};

Uncontrolled Input

import React, { useRef } from "react";
const UncontrolledInput = () => {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
if (inputRef.current) {
console.log(inputRef.current.value);
}
};
return <input ref={inputRef} />;
};

When to use each?

  • Controlled: When state should be fully managed by React.
  • Uncontrolled: When integrating with non-React libraries (e.g., form libraries).

3. Avoid Prop Drilling with Context or Render Props

Prop drilling occurs when state is passed down multiple layers, making the code difficult to manage.

Bad: Deep Prop Drilling

const GrandChild = ({ theme }: { theme: string }) => <p>Theme: {theme}</p>;
const Child = ({ theme }: { theme: string }) => <GrandChild theme={theme} />;
const Parent = () => {
const theme = "dark";
return <Child theme={theme} />;
};

Solution: Use Context API

import React, { createContext, useContext } from "react";
const ThemeContext = createContext("light");
const GrandChild = () => {
const theme = useContext(ThemeContext);
return <p>Theme: {theme}</p>;
};
const Parent = () => (
<ThemeContext.Provider value="dark">
<GrandChild />
</ThemeContext.Provider>
);

Context API removes unnecessary prop drilling.


4. Optimizing Performance with useMemo and React.memo

Prevent Unnecessary Re-Renders with React.memo

const ExpensiveComponent = React.memo(({ value }: { value: number }) => {
console.log("Re-rendered");
return <p>{value}</p>;
});

Optimize Expensive Computations with useMemo

import React, { useMemo } from "react";
const ExpensiveCalculation = ({ number }: { number: number }) => {
const computedValue = useMemo(() => number ** 2, [number]);
return <p>Computed Value: {computedValue}</p>;
};

5. Accessibility Best Practices

Use Semantic HTML

Bad:

<div onClick="{()" ="">alert("Clicked")}>Click Me</div>

Good:

<button onClick="{()" ="">alert("Clicked")}>Click Me</button>

Provide Accessible Labels

<label htmlFor="email">Email</label>
<input id="email" type="email" />

Manage Keyboard Navigation

<button onKeyDown={(e) => e.key === "Enter" && alert("Clicked")}>
Click Me
</button>

Conclusion

  • Use composition over inheritance for better flexibility.
  • Choose controlled vs uncontrolled inputs based on requirements.
  • Avoid prop drilling with Context API or render props.
  • Optimize performance using React.memo and useMemo.
  • Ensure accessibility with semantic HTML and proper keyboard handling.

By following these best practices, React components become scalable, maintainable, and efficient.