DEV Community

Cover image for Part 3: Component Structure - Building Reusable and Maintainable Components in React!
Sathish Kumar N
Sathish Kumar N

Posted on

Part 3: Component Structure - Building Reusable and Maintainable Components in React!

Welcome to Part 3 of our "React Best Practices in 2023" series! In this part, we will explore the importance of component structure and how it contributes to creating components that are highly reusable, modular, and easy to maintain.

Building reusable and maintainable components in React is not just about writing code; it's about adopting best practices and following sound architectural principles.

By carefully structuring our components, adhering to the Single Responsibility Principle, and embracing concepts like Atomic Design and Component Composition, we can create code that is more modular, easier to test, and simpler to maintain.

This approach leads to a more efficient development process and ultimately results in high-quality, scalable React applications.

Let's consider an example where we have a Todo application implemented in React.

// ❌ Bad code with multiple responsibilities
import React, { useState } from 'react';

const TodoApp = () => {

  // Handling state ❌ 
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');

  // Handle input change ❌ 
  const handleInputChange = (e) => {

 // Handle todo logic ❌ 
  const handleAddTodo = () => {
    if (newTodo.trim() !== '') {
      const updatedTodos = [...todos, newTodo];

  const handleDeleteTodo = (index) => {
    const updatedTodos = todos.filter((_, i) => i !== index);

  const handleCompleteTodo = (index) => {
    const updatedTodos =, i) => {
      if (i === index) {
        return { ...todo, completed: !todo.completed };
      return todo;

  // ❌  It doesn't provide a clear separation of smaller reusable components. 
  return (
      <h1>Todo App</h1>
      <input type="text" 
value={newTodo} onChange={handleInputChange} />
      <button onClick={handleAddTodo}>Add Todo</button>
        {, index) => (
          <li key={index}>
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.text}</span>
            <button onClick={() => handleDeleteTodo(index)}>Delete</button>
            <button onClick={() => handleCompleteTodo(index)}>
              {todo.completed ? 'Mark Incomplete' : 'Mark Complete'}
Enter fullscreen mode Exit fullscreen mode

Above codebase contains a single component that handles everything from rendering the UI to handling data and state management. This monolithic approach leads to a lack of separation of concerns and violates the SRP and Atomic Design principles.

To improve the code, we can follow the SRP and Atomic Design principles:

Single Responsibility Principle (SRP)

This principle states that a class or component should have a single responsibility or single reason to change. By keeping components focused on a specific task, you improve code readability, maintainability, and reusability.

It promotes breaking down complex functionality into smaller, focused parts that are easier to understand, test, and maintain.

It encourages components to have clear and specific responsibilities, enhancing their reusability and maintainability.

It helps in avoiding tightly coupled components by keeping them focused on specific tasks.

Let's breakdown the monolith,

  • TodoInput: Extract the input handling logic into a separate useTodoInput custom hook and component TodoInput.

Responsible for handling user input and adding new todos.

  • TodoList: Extract the todo list handling logic into a separate useTodoList custom hook and component TodoList.

Responsible for rendering the list of todos.

  • TodoItem: Move the rendering logic for individual todos into a separate TodoItem component.

Responsible for rendering an individual todo item.

By separating the state and event handling logic into custom hooks or components, we ensure that each component has a following single responsibility.

Todo Input

The useTodoInput custom hook can manage the input state using the useState hook and handle the input change event


// ✅ Responsible for manage state and UI events

import { useState } from "react";

const useTodoInput = (onAddTodo) => {
  const [inputValue, setInputValue] = useState("");
  const [disabled, setDisabled] = useState(true);

  const handleSubmit = (e) => {

  const handleInputChange = (e) => {
    const value =;
    setDisabled(value.trim() === "");

  const clearInput = () => {

  return {

export { useTodoInput };
Enter fullscreen mode Exit fullscreen mode

By utilizing custom hooks, we can encapsulate the state and event handling logic in a reusable and modular way, promoting code reusability and maintainability.


Move the JSX code related to the input field, "Add Todo" button, and todo list into separate JSX file.

// TodoInput.jsx

// ✅ Responsible for rendering TodoInput UI

const TodoInput = ({ onAddTodo }) => {
  const {
  } = useTodoInput(onAddTodo);

  return (
    <form className="todo-input" onSubmit={handleSubmit}>
        placeholder="Add a todo"
        className={`add-button ${disabled ? "disabled" : ""}`}
Enter fullscreen mode Exit fullscreen mode

By separating the JSX code into individual files, we can improve code organization and readability, making it easier to maintain and understand the component structure.

Like this we need to split our TodoItem and TodoList.

This refactoring approach adheres to the SRP by assigning single responsibilities to each component, utilizes custom hooks for state and event handling, and separates the JSX code into reusable components, promoting modularity and maintainability in the React application.

Finally, the component structure will look like below,

// ✅ Component Stucture

├── todo-input/
   ├── TodoInput.jsx
   ├── useTodoInput.js
   └── TodoInput.css
├── todo-item/
   ├── TodoItem.jsx
   └── TodoItem.css
├── todo-list/
   ├── TodoList.jsx
   ├── useTodoList.js
   └── TodoList.css
└── ...
Enter fullscreen mode Exit fullscreen mode

You can check it out the whole codebase in codesandbox.

We can further refactor this codebase using Atomic Design principles.

Atomic Design Principles

Atomic Design is a methodology for designing and organizing components in a hierarchical manner based on their level of abstraction and complexity.

It classifies components into five levels: Atoms, Molecules, Organisms, Templates, and Pages, with each level having a specific responsibility.

  • Atoms: At the lowest level, atoms represent the smallest and most basic UI elements, such as buttons, inputs, or icons.

They have a single responsibility, focusing on their visual appearance and basic functionality.

  • Molecules: Molecules are combinations of atoms that work together to create more complex UI elements.

They have a slightly higher level of responsibility, representing a group of related atoms.

  • Organisms: Organisms are composed of molecules and atoms, representing larger and more self-contained sections of a user interface.

They have more complex behavior and may include state management and interaction logic.

  • Templates: Templates are specific arrangements of organisms that provide a basic structure for a page or section.

They define the overall layout and composition of the UI.

  • Pages: Pages are instances where templates are populated with real data, creating actual content for the user to interact with.

Let's take an example of same todo app. I will give an high level code design using the Atomic Design Pattern:


Atoms contains small, reusable UI components like Button and Input.

// ✅ Atoms

// Button.jsx
const Button = ({ onClick, children }) => {
  return (
    <button className="button" onClick={onClick}>

const Input = ({ value, onChange }) => {
  return (
    <input className="input" type="text" value={value} onChange={onChange} />
Enter fullscreen mode Exit fullscreen mode

Each atom has its own JavaScript file (Button.jsx, Input.jsx) and CSS file (Button.css, Input.css).


The molecules directory contains combinations of atoms (Button.jsx) that form more complex components, such as the the TodoItem component.

// ✅ Molecules

// TodoItem.jsx
const TodoItem = ({ todo, onDelete, onComplete }) => {
  return (
    <li className="todo-item">
      <span className={todo.completed ? 'completed' : ''}>{todo.text}</span>
      <Button onClick={onDelete}>Delete</button>
      <Button onClick={onComplete}>
        {todo.completed ? 'Mark Incomplete' : 'Mark Complete'}
Enter fullscreen mode Exit fullscreen mode

It has its own JavaScript file (TodoItem.js) and CSS file (TodoItem.css).


The organisms directory contains larger, more feature-rich components, such as the TodoForm and TodoList components.

// ✅ Organisms

// TodoForm.jsx
const TodoForm = ({ onAddTodo }) => {
  const {inputChange, addTodo} = useTodoForm();

  return (
    <div className="todo-form">
      <Input value={newTodo} onChange={inputChange} />
      <Button onClick={addTodo}>Add Todo</Button>

// TodoList.jsx
const TodoList = ({ todos, onDeleteTodo, onCompleteTodo }) => {
  return (
    <ul className="todo-list">
      {, index) => (
          onDelete={() => onDeleteTodo(index)}
          onComplete={() => onCompleteTodo(index)}
Enter fullscreen mode Exit fullscreen mode

They are composed of molecules and/or atoms and have their own JSX(TodoForm.jsx, TodoList.jsx), Custom Hooks(useTodoForm.js) and CSS files.


The templates contains components that provide the overall structure of a page or layout. In this case, the Todo template is responsible for rendering the TodoForm and TodoList components.

// ✅ Templates

// Todo.jsx
const Todo = () => {
  const { 
  } = useTodo();

  return (
    <div className="todo-app">
      <h1>Todo App</h1>
      <TodoForm onAddTodo={addTodo} />
Enter fullscreen mode Exit fullscreen mode

It has its own JSX file (Todo.jsx) and Custom Hook (useTodo.js) and CSS file (Todo.css).


The pages directory components that represent a specific page in the application. In this example, there is a HomePage component that serves as the main entry point of the Todo app.

// ✅ Pages

// HomePage.js
const HomePage = () => {
  return (
    <div className="home-page">
      <TodoApp />
Enter fullscreen mode Exit fullscreen mode

This example demonstrates how the Todo app codebase can be structured using the Atomic Design pattern. Each component is responsible for a single concern, and they can be easily reused and composed to build the complete Todo app.

Final Thoughts

When designing your React app, it's essential to avoid assigning multiple responsibilities to a single component. Here are some practical strategies to help you achieve a cleaner and more maintainable codebase:

1. Identify clear responsibilities: Clearly define the purpose of each component. Break down complex functionalities into smaller, focused components with well-defined responsibilities.

2. Separation of concerns: Separate concerns by dividing your app into distinct components based on their functionality. Each component should have a specific role and handle a single responsibility.

3. Component composition: Instead of building large components that handle multiple tasks, compose your UI by combining smaller, reusable components. This promotes reusability and modularity.

4. Single-task functions: Extract complex logic from components into separate functions or utility modules. By encapsulating specific functionalities in separate functions, you keep your components focused on rendering and UI-related tasks.

5. Follow the SOLID principles: Adhere to SOLID principles, such as the Single Responsibility Principle (SRP), which states that a component should have only one reason to change. This principle helps you design components that are focused, maintainable, and easier to test.

6. Use custom hooks: Extract common logic into custom hooks that can be shared across components. This allows you to reuse logic without introducing unnecessary complexity to individual components.

7. Modular architecture: Organize your codebase using a modular architecture, such as the feature-based folder structure. This approach promotes separation of concerns and helps in keeping components focused on their specific responsibilities.

By consciously designing your React app with these practices in mind, you can avoid assigning multiple responsibilities to components. This leads to cleaner, more maintainable code that is easier to understand, test, and extend.

Bonus - Component Hierarchy

It is generally recommended to follow a specific component hierarchy to maintain consistency and readability in your codebase.

// ✅ Component Hierarchy

// External dependencies
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';

// Internal dependencies
import { TodoItem } from './TodoItem';
import { TodoUtils } from '../utils';
import { useTodo } from '../hooks';
import { withTimer } from '../hoc';
import { TodoType } from '../enums';

// Stylesheets
import './Component.css';
import '../styles/common.css';

// Assets
import todoImage from '../assets/todoImage.png';

const Todo = () => {
  // State logic
  const [todos, setTodos] = useState([]);

  // Ref
  const inputRef = useRef(null);

  // Variable
  const title = 'Todo List';

  // Custom hook
  const {addTodo} = useTodo();

  // Higher-order component
  const timer = 

  // Component lifecycle methods (useEffect)
  useEffect(() => {
  }, []);

  // Component render
  return (
      {/* Component JSX */}

Todo.propTypes = {
  // Prop types declaration

export { Todo };
Enter fullscreen mode Exit fullscreen mode

By structuring your component hierarchy in a consistent and organized manner, you can improve the readability, maintainability, and scalability of your React app.

A well-defined hierarchy helps developers navigate the codebase, understand component relationships, and make modifications efficiently.

Stay tuned for more tips and tricks on building high-quality React applications in my future blog posts!

Happy coding!😊👩‍💻👨‍💻

Top comments (6)

yoglib profile image
Yogev Boaron Ben-Har

I really like the concept of Atomic Design, it really makes the code a low more readable and testable...

patzi275 profile image
Patrick Zocli

Congratulations, it's very informative. But I would have preferred you to briefly introduce us to the structure of the app before coming to the code. But it's really instructive.

sathishskdev profile image
Sathish Kumar N • Edited

Thank you for your comment!

rv90904 profile image
Ramesh Vishnoi

Great article. You explained very important concepts which usually are not easily available and most neglated as well.

I liked the Atomic design principles concepts. Keep up the good work.

ekqt profile image
Hector Sosa

Solid article and very well resourced!

muditchoudhary profile image
Mudit Choudhary

Thank you for this detailed guide I appreciate it. I understood both principles quite good with how you explained. I'm working on a personal project. I struggled a lot with code design and folder structure.