Back to Articles

5 React Architecture Patterns Used at Top Tech Companies

ClaudiaJune 15, 20259 min read

The difference between hobby projects and production applications isn't just scale—it's architecture. Here are five patterns that top tech companies use to keep their React applications maintainable as they grow.

1. Container/Presentational Pattern

This classic pattern separates components into two types: container components that handle logic and data, and presentational components that handle UI.

How it works: Container components fetch data, manage state, and handle events. They pass everything to presentational components as props. Presentational components just render what they're given—no state, no side effects.

Why it matters: This separation makes components easier to test, reuse, and reason about. You can change how data is fetched without touching UI code, and vice versa.

Modern variation: With hooks, the container is often a custom hook rather than a wrapper component. The principle remains: separate what data you need from how you display it.

2. Feature-Based Folder Structure

Instead of organizing by type (components/, hooks/, utils/), organize by feature or domain (auth/, dashboard/, settings/).

How it works: Each feature folder contains everything it needs: components, hooks, utilities, types, tests. Shared code lives in a separate common/ or shared/ folder.

Why it matters: When working on a feature, all relevant code is in one place. When removing a feature, you delete one folder. When onboarding new developers, they can focus on one feature without understanding the entire codebase.

The tradeoff: Some code doesn't fit neatly into one feature. The art is deciding what's truly shared vs. what just feels shared because features aren't well-defined.

3. Compound Components

Compound components are a set of components that work together to form a complete UI element, sharing implicit state between them.

How it works: A parent component provides context, and child components consume it. The classic example is a <Select> component with <Option> children. The parent tracks which option is selected; children register themselves and respond to selection changes.

Why it matters: Users get flexible APIs without prop drilling. Instead of passing arrays of options to configure a component, they compose the structure they need. The result is more readable, more flexible, and easier to extend.

Real-world examples: Radix UI, Headless UI, and Reach UI all use this pattern extensively. It's how you build components that are both powerful and pleasant to use.

4. State Machines for Complex UI

For UIs with complex state transitions—multi-step forms, async operations with many states, interactive flows—state machines provide explicit, predictable state management.

How it works: Define all possible states, all possible events, and the valid transitions between them. The UI can only be in defined states, and can only transition through defined events. Impossible states become actually impossible.

Why it matters: Boolean flags multiply combinatorially. With isLoading, isError, and isSuccess, you have 8 possible combinations—but only 3 make sense. State machines make invalid states unrepresentable, eliminating entire classes of bugs.

Tools: XState is the most popular library for state machines in React. For simpler cases, useReducer with a discriminated union type can achieve similar benefits.

5. Server State vs. Client State Separation

Not all state is the same. Server state (data from your API) has different characteristics than client state (UI state, form inputs, user preferences). Treating them the same creates unnecessary complexity.

How it works: Use a server state library (React Query, SWR, Apollo) for data from your API. Use local state, context, or a simple store for UI state. Don't put server data in Redux; don't fetch data with useEffect.

Why it matters: Server state libraries handle caching, revalidation, optimistic updates, and synchronization—all the hard parts of working with remote data. Separating concerns lets each tool do what it does best.

The insight: Most applications have very little true client state. What feels like complex state management is often just server state synchronization done manually. Using the right tools can eliminate most of that complexity.

Applying These Patterns

These patterns aren't rules to follow blindly. They're tools for solving specific problems:

  • Container/Presentational: When you need to reuse UI with different data sources
  • Feature-based structure: When your codebase grows beyond a few thousand lines
  • Compound components: When building reusable UI components with complex configuration
  • State machines: When UI state transitions are complex or error-prone
  • Server/client state separation: Always, basically

Start simple. Add patterns when they solve real problems you're experiencing, not problems you might have someday.

Learning Architecture Through Practice

Reading about patterns is different from applying them. The best way to internalize these patterns is to build projects complex enough to need them.

That's why we created FrontendCheck—challenges that simulate real enterprise development where these patterns naturally emerge as solutions. You don't learn state machines because someone told you to; you learn them because you hit the problems they solve.

Key takeaway: Enterprise React applications use patterns like container/presentational separation, feature-based organization, compound components, state machines, and server/client state separation. These patterns emerge from solving real problems—learn them through practice, not just theory.