Building Scalable React Components
/ 3 min read
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.