Component architecture
Designing a robust component architecture is crucial for building scalable and maintainable React applications. By adhering to best practices, developers can ensure their applications are efficient, organized, and easy to manage. Below are some key recommendations to guide your React component architecture.
1. Organize project structure effectively
As outlined in the project structure section, each component should live in its own folder for better organization, isolation, and scalability. The recommended structure for a component folder is as follows.
component-name/
├── component-name.stories.tsx // Storybook file for documenting and showcasing the component
├── index.ts // Entry point for importing/exporting the component
├── types.ts // TypeScript types and interfaces used in the component
├── hooks.ts // Custom hooks related to the component
├── someFunction.ts // Utility functions used exclusively by the component.
└── ... // Additional files specific to the component
Always co-locate tests, stories, and types with the component. This keeps everything related to a component in one place and simplifies maintainability.
2. Adhere to the Single Responsibility Principle (SRP)
Ensure that each component has a single, well-defined responsibility. Components should be small and focused, handling one piece of functionality or UI. This practice enhances reusability and simplifies testing.
Example: Instead of a single large UserProfile component, break it down:
ProfilePicture: Renders the user’s photo.UserName: Displays the name.UserBio: Shows additional user details.
This decomposition improves readability and allows individual updates without affecting the entire profile UI.
3. Keep components small and focused
Avoid “god components” that handle too much logic or rendering. Aim to create components that are small, focused, and responsible for a single piece of functionality or UI. This practice enhances reusability and simplifies testing.
- Write presentational components that focus on UI.
- Write container components (or hooks) that manage logic, data fetching, and state.
Guideline: If a component is getting larger than ~200 lines, consider breaking it into smaller subcomponents or extracting logic into custom hooks.
4. Utilize custom hooks for reusable logic and separate business logic from UI
- When multiple components share similar logic, encapsulate that logic within custom hooks. This approach promotes code reuse and keeps components clean and focused on their primary responsibilities.
- Decouple business logic from UI components to enhance testability and maintainability. Manage business logic in separate modules or custom hooks, allowing UI components to focus more on rendering.
For example:
Custom hook for fetching user data.
import { useState, useEffect } from 'react';
function useUserData(userId) {
const [userData, setUserData] = useState(null);
useEffect(() => {
fetchUserData(userId).then(data => setUserData(data));
}, [userId]);
return userData;
}
Then use it in a component.
function UserProfile( { userId } ) {
const user = useUserData( userId );
if ( !user ) {
return <p>Loading...</p>
};
return <div>{ user.name }</div>;
}
This way we can keep business logic separate (hooks, services, utils) so that components remain pure and primarily handle rendering.
5. Keep components pure
As emphasized on react.dev, components should behave like pure functions. A pure function is one that:
- Minds its own business: No modifying external variables or global state during rendering.
- Consistent output: The same inputs (props/state/context) should always produce the same output.
- No side effects in render: Avoid API calls, subscriptions, or global modifications inside render logic.
Instead:
- React components should rely solely on their inputs props, state, and context, to determine their output. This means avoiding side effects during rendering, such as modifying external variables or performing operations that affect the global state.
- Handle user actions with event handlers, which update the component’s state. React then re-renders the component to reflect these state changes.
- Update state via React hooks.
- Let React re-render components based on state, prop, or context changes.
This leads to predictable, testable, and bug-resistant applications.
Additional recommendations
- Prop-Driven Components: Favor props over relying too much on context/global state. This improves reusability and makes components easier to test in isolation.
- Type Safety: Use TypeScript or PropTypes to define component contracts. This prevents misuse and improves developer experience.
- Error Boundaries: Wrap critical UI pieces with error boundaries to gracefully handle failures.







