Logo

August 6, 2025

Why Your React Components Are Too Smart (And How to Fix Them)

The sneaky architecture problem that’s making your codebase harder to maintain

Written By

Ajay Gupta

Your React components are doing too much. I can say this with confidence because after reviewing hundreds of codebases, I see the same pattern everywhere: components that started simple but gradually accumulated responsibilities until they became unmaintainable monsters.

The worst part? This happens so gradually that teams don’t notice until they’re already in pain.

The Smart Component Trap

Here’s how it usually starts. You build a UserProfile component that fetches user data and displays it. Simple enough:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId)
      .then(setUser)
      .finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <Spinner />;

  return (
    <div>
      <img src={user.avatar} alt={user.name} />
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Six months later, that same component looks like this:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [editing, setEditing] = useState(false);
  const [preferences, setPreferences] = useState({});
  const [notifications, setNotifications] = useState([]);
  const [showNotifications, setShowNotifications] = useState(false);

  useEffect(() => {
    Promise.all([
      fetchUser(userId),
      fetchPreferences(userId),
      fetchNotifications(userId),
    ]).then(([userData, prefs, notifs]) => {
      setUser(userData);
      setPreferences(prefs);
      setNotifications(notifs);
      setLoading(false);
    });
  }, [userId]);

  const handleEdit = () => setEditing(true);
  const handleSave = (data) => {
    updateUser(userId, data).then(() => {
      setUser(data);
      setEditing(false);
    });
  };

  const handleNotificationClick = (id) => {
    markAsRead(id);
    setNotifications((prev) =>
      prev.map((n) => (n.id === id ? { ...n, read: true } : n))
    );
  };

  // ... 150 more lines of logic
}

Sound familiar? This component is now responsible for data fetching, state management, user interactions, notifications, preferences, and presentation. It’s become a “god component” that’s impossible to test, reuse, or modify safely.

The Real Cost of Smart Components

1. Testing Becomes a Nightmare

When components do everything, you can’t test individual pieces. Want to test the notification logic? You have to mock the entire user fetching flow. Want to test the edit functionality? Same problem.

2. Reusability Dies

Need a simple user display somewhere else? Too bad — your component comes with all the editing, notifications, and preferences baggage. You end up copying and pasting pieces instead of reusing components.

3. Performance Takes a Hit

Smart components often re-render unnecessarily because they’re subscribed to too much state. Change a notification status? The entire profile re-renders, including expensive user data processing.

4. Debugging Becomes Detective Work

When something breaks, you have to trace through multiple responsibilities to find the culprit. Is it the data fetching? State updates? User interactions? Good luck.

Here’s how I restructure that UserProfile component:

1. Container Component (Smart)

Handles data fetching and coordinates between pieces:

function UserProfileContainer({ userId }) {
  const { user, loading } = useUser(userId);
  const { preferences } = usePreferences(userId);
  const { notifications } = useNotifications(userId);

  if (loading) return <LoadingSpinner />;

  return (
    <div>
      <UserDisplay user={user} />
      <UserEditor user={user} onSave={handleSave} />
      <NotificationList
        notifications={notifications}
        onNotificationClick={handleNotificationClick}
      />
    </div>
  );
}

2. Presentation Components (Dumb)

Each focused on a single UI concern:

function UserDisplay({ user }) {
  return (
    <div>
      <img src={user.avatar} alt={user.name} />
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

function UserEditor({ user, onSave }) {
  const [editing, setEditing] = useState(false);
  // editing logic only
}

function NotificationList({ notifications, onNotificationClick }) {
  // notification display logic only
}

3. Custom Hooks (Business Logic)

Extract reusable logic into custom hooks:

function useUser(userId) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId)
      .then(setUser)
      .finally(() => setLoading(false));
  }, [userId]);

  return { user, loading };
}

The Component Responsibility Checklist

Before you add any new feature to a component, ask:

If you answer yes to any of these, it’s time to extract.

Practical Refactoring Strategy

Don’t try to fix everything at once. Use this incremental approach:

Week 1: Identify the Worst Offenders

Look for components with:

Week 2: Extract Custom Hooks

Move business logic into custom hooks. This immediately makes logic reusable and testable.

Week 3: Split UI Concerns

Break complex render methods into focused presentation components.

Week 4: Create Container/Presentation Pairs

Separate data fetching from display logic.

The Long-Term Payoff

Teams that consistently apply this pattern report:

Common Pushback and Responses

“This creates too many files!” — Yes, you’ll have more files. But you’ll also have files you can actually understand and modify confidently.

“It’s more overhead for simple features!” — True for the first implementation. But the second time you need similar functionality, you’ll reuse existing pieces and move much faster.

“Our designers won’t like the extra complexity!” — Your designers care about user experience, not file count. Better component architecture leads to more consistent UI and faster iterations.

Start Today

Pick your most problematic component. The one that makes everyone groan when they have to modify it. Apply this checklist:

Extract one piece. See how much easier it becomes to work with. Then do it again.

Your future self (and your teammates) will thank you.