React  

Designing a Clean React Architecture: Understanding Props, Components, and Page Logic (With Practical Example)

When building React applications, beginners often write everything inside a single component. While this may work for small projects, real-world applications require structured and maintainable architecture.

Professional React projects usually separate code into:

  • API Layer

  • Service Layer

  • Page Components

  • Reusable UI Components

This separation keeps the application organized, scalable, and easy to maintain.

In this article, we will understand the concept and examine the actual code implementation using an Employee Management example.

1. How a Structured React Application Works

A well-designed React application follows this flow:

Backend API

Axios API Layer

Service Layer

Page Component (Logic & State)

UI Components (Display)

This approach ensures that each layer has a clear responsibility.

2. Project Folder Structure

A clean project structure might look like this:

src
│
├── api
│   └── axiosClient.js
│
├── services
│   └── employeeService.js
│
├── components
│   ├── Header.jsx
│   ├── EmployeeForm.jsx
│   └── EmployeeTable.jsx
│
├── pages
│   └── EmployeePage.jsx
│
├── App.js
└── index.js

Let’s understand each part with code.

3. API Layer (Axios Configuration)

The API layer centralizes API configuration.

File:

src/api/axiosClient.js

import axios from "axios";

const axiosClient = axios.create({
  baseURL: "http://localhost:5288/api",
  headers: {
    "Content-Type": "application/json"
  }
});

export default axiosClient;

Why this layer is useful

Instead of repeating API configuration everywhere, we create it once and reuse it across the application.

Later, this file can also be used for:

  • Adding JWT tokens

  • Error handling

  • Interceptors

4. Service Layer (Business API Calls)

The service layer communicates with the backend API.

File:

src/services/employeeService.js
import axiosClient from "../api/axiosClient";

export const getEmployees = () => {
  return axiosClient.get("/Employee");
};

export const addEmployee = (data) => {
  return axiosClient.post("/Employee", data);
};

export const updateEmployee = (id, data) => {
  return axiosClient.put(`/Employee/${id}`, data);
};

export const deleteEmployee = (id) => {
  return axiosClient.delete(`/Employee/${id}`);
};

Why this layer is important

The UI components do not call Axios directly. Instead, they call service functions.

This improves:

  • Code readability

  • Maintainability

  • Reusability

5. Header Component

The header is a simple UI component.

File:

src/components/Header.jsx
function Header() {
  return (
    <div className="bg-blue-500 text-white p-6 rounded shadow text-3xl text-center mb-6">
      Employee Management
    </div>
  );
}

export default Header;

This component only handles UI display.

6. Employee Form Component

This component manages input fields and buttons.

File:

src/components/EmployeeForm.jsx
function EmployeeForm({
  name,
  department,
  setName,
  setDepartment,
  addEmployee,
  updateEmployee
}) {
  return (
    <div className="flex gap-4 mb-6">

      <input
        placeholder="Name"
        className="border px-4 py-2 rounded flex-1"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />

      <input
        placeholder="Department"
        className="border px-4 py-2 rounded flex-1"
        value={department}
        onChange={(e) => setDepartment(e.target.value)}
      />

      <button
        onClick={addEmployee}
        className="bg-green-500 text-white px-4 py-2 rounded"
      >
        Add
      </button>

      <button
        onClick={updateEmployee}
        className="bg-yellow-500 text-white px-4 py-2 rounded"
      >
        Update
      </button>

    </div>
  );
}

export default EmployeeForm;

This component receives data and functions from the parent component through props.

7. Employee Table Component

This component displays employee data.

File:

src/components/EmployeeTable.jsx
function EmployeeTable({ employees, editEmployee, deleteEmployee }) {
  return (
    <div className="overflow-x-auto bg-white p-4 rounded shadow">

      <table className="w-full border">

        <thead className="bg-gray-200">
          <tr>
            <th className="border px-4 py-2">ID</th>
            <th className="border px-4 py-2">Name</th>
            <th className="border px-4 py-2">Department</th>
            <th className="border px-4 py-2">Actions</th>
          </tr>
        </thead>

        <tbody>
          {employees?.map(emp => (
            <tr key={emp.id}>

              <td className="border px-4 py-2">{emp.id}</td>
              <td className="border px-4 py-2">{emp.name}</td>
              <td className="border px-4 py-2">{emp.department}</td>

              <td className="border px-4 py-2">

                <button
                  onClick={() => editEmployee(emp)}
                  className="bg-blue-500 text-white px-3 py-1 mr-2 rounded"
                >
                  Edit
                </button>

                <button
                  onClick={() => deleteEmployee(emp.id)}
                  className="bg-red-500 text-white px-3 py-1 rounded"
                >
                  Delete
                </button>

              </td>

            </tr>
          ))}
        </tbody>

      </table>

    </div>
  );
}

export default EmployeeTable;

This component focuses purely on displaying the data.

8. Page Component (Main Logic Controller)

The page component manages:

  • State

  • API calls

  • CRUD operations

  • Passing props to components

File:

src/pages/EmployeePage.jsx
import React, { useEffect, useState } from "react";
import Header from "../components/Header";
import EmployeeForm from "../components/EmployeeForm";
import EmployeeTable from "../components/EmployeeTable";

import {
  getEmployees,
  addEmployee,
  updateEmployee,
  deleteEmployee
} from "../services/employeeService";

function EmployeePage() {

  const [employees, setEmployees] = useState([]);
  const [id, setId] = useState(null);
  const [name, setName] = useState("");
  const [department, setDepartment] = useState("");

  useEffect(() => {
    loadEmployees();
  }, []);

  const loadEmployees = () => {
    getEmployees()
      .then(res => setEmployees(res.data))
      .catch(err => console.log(err));
  };

  const handleAdd = () => {
    const data = { name, department };

    addEmployee(data)
      .then(() => {
        loadEmployees();
        setName("");
        setDepartment("");
      });
  };

  const handleDelete = (id) => {
    deleteEmployee(id).then(() => loadEmployees());
  };

  const handleEdit = (emp) => {
    setId(emp.id);
    setName(emp.name);
    setDepartment(emp.department);
  };

  const handleUpdate = () => {
    const data = { id, name, department };

    updateEmployee(id, data)
      .then(() => {
        loadEmployees();
        setId(null);
        setName("");
        setDepartment("");
      });
  };

  return (
    <div className="min-h-screen bg-gray-100 p-6">

      <Header />

      <EmployeeForm
        name={name}
        department={department}
        setName={setName}
        setDepartment={setDepartment}
        addEmployee={handleAdd}
        updateEmployee={handleUpdate}
      />

      <EmployeeTable
        employees={employees}
        editEmployee={handleEdit}
        deleteEmployee={handleDelete}
      />

    </div>
  );
}

export default EmployeePage;

This file acts as the central controller of the application.

9. Key React Principle Demonstrated

This project demonstrates the concept of One-Way Data Flow.

Data moves like this:

API

Service Layer

Page Component

UI Components

Components receive data through props and trigger actions through callback functions.

10. Conclusion

Designing React applications is not just about writing code that works. It is about organizing the application in a way that remains maintainable as it grows.

By separating the application into:

  • API Layer

  • Service Layer

  • Page Components

  • UI Components

you create a scalable architecture that is widely used in professional React applications.

This approach ensures:

  • Clean separation of concerns

  • Reusable components

  • Easier debugging

  • Scalable project structure

Following these principles will help you build React applications that are like to real-world production systems.