Structuring React Projects the Smart Way: A Comprehensive Guide to CoreUI Based Architecture
Table of contents
Introduction
React has become a popular choice for building user interfaces for web applications. However, as the size and complexity of React projects grow, it becomes increasingly challenging to maintain and improve performance. This is where having a well-organized and modern structure for your React project becomes crucial.
One of the most crucial steps in developing a successful React project is to plan its architecture before diving into writing code. This means creating a blueprint of the entire project from the start and anticipating how it might perform in various scalability scenarios.
It’s important to approach the project from an architectural standpoint, as doing so can help prevent potential issues down the road. By creating a solid plan for the project, you can ensure that it will be scalable, maintainable, and efficient.
Therefore, as a software engineer, I can provide valuable input in creating an effective architecture for a project. By leveraging my knowledge and expertise, I can help provide a structure that might be a good solution for specific cases.
before we dive deep into building and planning the structure, let’s first of all have a quick introduction about project structuring and how we can benefit of.
Project structuring refers to the organization of a codebase by defining a clear hierarchy of files and directories, following some standards and design patterns to make the codebase more manageable and accessible.
Why do we need to structure our projects?, can’t we just get the requirements and start coding?
having a well-structured project is crucial for scalability, maintainability, and efficiency. It involves organizing the codebase and defining a clear hierarchy of files and directories, which makes it easier to manage, maintain, and navigate the code. Good project structuring can also make the development process more enjoyable and lead to better software.
structuring the project can include several parts to work on, such as directories and files hierarchy, naming conventions, consistency, and following the best practices of writing the codebase itself, and here we will mainly focus on the directories and files part of structuring a React project, since dealing with this part can enhance our code management, accessibility, and scalability.
the initial folder structure of a new React project may be basic, whether created using a tool like create-react-app
or built from scratch. However, this simple structure may not be sufficient for larger, more complex projects that require greater scalability and accessibility.
so, After working on numerous React projects, I have developed a folder structure that I found to be scalable, accessible, and easy to manage.
I called this structure CoreUI Based Structure
, this structure is mainly built on the idea of layering
the components on top of each other.
but first, let’s talk a little about the Layering
concept that we mentioned before.
When building a project, utilizing a layered approach entails structuring each page with multiple independent layers. These layers are then combined upon rendering to create the desired page layout. This approach allows for greater flexibility and ease of modification, as each layer can be modified or updated without affecting the others. Ultimately, utilizing a layered approach streamlines the development process and enhances the overall quality and functionality of the project.
To further clarify, each layer should be stored in a separate and independent directory within the project. This ensures that modifications to one layer do not unintentionally affect other layers, making it easier to manage and update the project as a whole. By keeping each layer separate and organized, developers can quickly identify and address any issues that may arise, leading to a more efficient and effective development process.
So, we can think initially of those layers as 3 layers, the CoreUI Layer, Components Layer, and Pages/Views Layer, where each layer is included in the next one respectively, so we can say that the page holds one or more component and those components can hold one or more of the CoreUI components, as shown below:
So now, after we took a look at the layers, let’s talk a little about each one of them:
1 - Core UI:
The Core UI layer is a crucial component of any project, as it provides a set of commonly used components that are utilized throughout the entire application. These components should be designed with flexibility in mind, allowing them to be configured for a wide range of use cases without requiring changes to the underlying code.
To achieve this, it’s important to write generic code that can be easily customized using props, rather than hard-coding specific cases. Examples of commonly used components in the Core UI layer include buttons, inputs, text areas, dropdowns, selects, radios, checkboxes, modals, and toasts.
By developing a strong Core UI layer, developers can save time and effort by reusing common components across the application. This approach also helps ensure consistency and maintainability throughout the project, as changes to the Core UI layer can be made in one place and propagated across the entire application.
2 - Components:
The component layer is a familiar component of most projects, consisting of components that are specifically designed for use on individual pages. In our case, these components can include any necessary Core UI components, but are built to meet the unique needs of a particular page or group of pages.
Unlike the Core UI layer, the component layer may require changes to the underlying code to meet specific requirements. However, by building these components as modular, reusable units, developers can still save time and effort in the long run by reusing components across multiple pages.
In summary, the component layer is an important part of the overall project design, providing tailored solutions for individual pages or groups of pages while still leveraging the Core UI layer to achieve consistency and maintainability across the application.
3 - Pages/Views:
The Pages/Views layer is the final layer in the process of building a page or view. This layer allows developers to combine a group of components, which can in turn leverage a variety of Core UI components to achieve a specific layout or design.
From a code perspective, the Pages/Views layer is relatively lightweight, serving mainly as a collection point for the components included on a given page or view. The layer is primarily concerned with gathering and organizing the necessary components and props to render the desired layout, rather than implementing complex functionality or business logic.
- By using a layered approach that separates concerns between the Core UI, component, and Pages/Views layers, developers can more easily manage and modify each component separately, improving overall efficiency and maintainability of the project.
After examining the three layers, it becomes clear that the complexity of the code generally decreases as we move from the bottom to the top of the stack*. The CoreUI layer, being the **foundational layer, may contain the most complex code, with a high degree of customization and flexibility required to ensure that the CoreUI components can be utilized across the entire project.*
On the other hand, the Pages/Views layer is typically the simplest layer, mainly serving as a collection point for the components included on a given page or view. This layer focuses primarily on organizing the components and props needed to render the desired layout or design.
Now that we’ve discussed the three layers in detail, let’s use an example to help understand the concept more clearly:
Suppose we have a form that consists of two inputs, one checkbox, and one button. This form is part of a custom-designed card that only appears on one page. Additionally, the page should include a text component with a title and a description, which also has a custom design and appears in multiple places throughout the application.
– Using our approach, we can consider the inputs, button, checkbox, components as CoreUI components, and title/description component can be a Normal Component, and we will discuss why later.
– We know that our application can have multiple instances of these components, and that they can appear in different places.
The form card has custom designs that are specific to this page. Therefore, we can create a new component for the form and use the CoreUI components to create a custom component for this page.
On the other hand, when we consider the
title/description
component, we can observe that it is utilized in multiple instances. However, this component is not as complicated as we may initially perceive it to be. It simply comprises an element that displays the title and another that displays the description. Therefore, there is no need to include it in the coreUI layer since there aren’t any diverse use cases that require configuration across our project.so we can simply create a Normal Component called
title/description
and use it in several pages.
– Finally, we can combine the Form Card
and the title/description
component into a single component which is the page. We may need to add some styles to the page file to ensure that these components appear correctly next to each other.
as we will see in the steps below:
1- Creating the CoreUI components:
We agreed that we need to make the inputs, button, and the checkbox as coreUI components, so let’s create those components:
- Input:
const Input = ({ type, placeholder, value, onChange, name }) => {
return (
<input
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
name={name}/>
);
};
export default Input;
- Button:
const buttonColors = {
primary: "primary",
secondary: "secondary",
success: "success",
danger: "danger",
warning: "warning",
info: "info",
};
const Button = ({
color = "primary",
textContent = "core button",
onClick = () => {},
className = "",
disabled = false,
}) => {
return (
<button
disabled={disabled}
className={`core-button ${buttonColors[color]} ${className}`}
onClick={onClick}
style={{ backgroundColor: color }}
>
{textContent}
</button>
);
};
export default Button;
- Checkbox:
const Checkbox = ({
checked = false,
className = "",
label = "core checkbox",
onChange = () => {},
name = "",
}) => {
return (
<div className={`core-checkbox ${className}`}>
<input
name={name}
type="checkbox"
checked={checked}
onChange={onChange}
/>
<label>{label}</label>
</div>
);
};
export default Checkbox;
– As you can see, when creating the CoreUI component, we must strive to make them as configurable as possible. As demonstrated previously, for instance with the button component, we included a configuration option for its color, enabling us to modify it wherever it is employed. Furthermore, for the other component, we observed that all values, including class names, are dynamically bound. This allows us to use these components in various scenarios and situations, utilizing different configurations to meet our requirements
2 - Creating the Normal Components:
Now, we can create the title/description
, and the FormCard
components as normal components to use on several pages:
- TitleDescription
const TitleDescription = ({
title = "",
description = "",
containerClassName = "",
titleClassName = "",
descriptionClassName = "",
}) => {
return (
<div className={`title-description ${containerClassName}`}>
<h1 className={`title ${titleClassName}`}>{title}</h1>
<p className={`description ${descriptionClassName}`}>{description}</p>
</div>
);
};
export default TitleDescription;
– We can perceive that this component could be a part of the CoreUI component library. However, the distinction here is that this particular component does not have any use cases that require configuration. We only need to provide the title and description that we want, and it should display the content uniformly across all pages. Nevertheless, we could still include some configuration options to make it adaptable for different designs, as exemplified in the containerClassName
, titleClassName
, and descriptionClassName
.
- FormCard:
– let’s suppose that we name this component as LoginForm
import Input from "../../core/Input";
import Checkbox from "../../core/Checkbox";
import Button from "../../core/Button";
const LoginForm = ({ onSubmit = () => {} }) => {
const [loginData, setLoginData] = useState({});
const onChange = (e) => {
const { name, value } = e.target;
setLoginData({ ...loginData, [name]: value });
};
return (
<div className="login-form-card">
<Input
onChange={onChange}
name="username"
type="text"
placeholder="Username"
/>
<Input
onChange={onChange}
name="password"
type="password"
placeholder="Password"
/>
<Checkbox onChange={onChange} label="Remember me" />
<Button
disabled={!loginData.username || !loginData.password}
onclick={onSubmit}
textContent="Login"
/>
</div>
);
};
export default LoginForm;
– As you can observe in this component, we have consolidated the CoreUI components that we intend to use. We have implemented the necessary logic, state management, and functionality to retrieve the data and store it within this component.
3 - Creating the pages/views component:
- Page:
– let’s suppose that we name this page as Login
:
import LoginForm from "../../components/LoginForm";
const Login = () => {
const onSubmit = () => {
console.log("Login");
};
return (
<div className="login-page">
<LoginForm onSubmit={onSubmit} />
<TitleDescription
title='Welcome to our app!'
description="Login using username and password"
containerClassName='login-text-container'
titleClassName='login-title'
descriptionClassName='login-description' />
</div>
);
};
export default Login;
– In this final step of creating the structure, we can observe that this component solely assembles the Normal Components
that were previously constructed. Additionally, we can note that this component has the simplest code among all the components we have created so far.
- Overall, we can conclude that all of the components we have created are relatively small and straightforward. Even in scenarios where scalability is a concern, we can confidently assert that these components can remain small and uncomplicated due to the
layering structure
that this example project adheres to.
Note:
It’s important to note that we shouldn’t overuse CoreUI components.
Before creating a CoreUI component, we need to ensure that it meets certain criteria:
1- The component should be commonly used in multiple places throughout the application, not just in one or two places.
2 - The places where it’s used should have multiple cases, not just one case.
3 - Finally, the component should be a first layer component, meaning that it should not contain any complex components within it.
By following these guidelines, we can ensure that our use of CoreUI components is both efficient and effective.
Maintainability:
– Let’s say we’ve already used the CoreUI Based Structure
approach to build a project, and now we want to expand one of our coreUI components. Expanding a coreUI component can be tricky because we need to regression test all of the components that use it.
To avoid this headache, we need to make sure that we add new code based on the existing code, and that the code of our component is generic from the time it was built. This will ensure that we can expand our component without having to do extensive regression testing.
– Let’s consider a scenario where the code for a particular coreUI component is not generic, but we still need to expand it. In this case, we need to refactor the existing code of the component to make it more generic before we can expand it.
– Before expanding a non-generic component, we should analyze the code, remove any hardcoded values, eliminate duplications, and make the code more flexible and adaptable to different use cases. After completing this step, we can add our new code to expand the component. It’s important to then conduct regression testing on all components that use this expanded component to ensure it works correctly.
– Note that we haven’t discussed the components and the pages/view layers. As we mentioned earlier, these components are generally simpler and built for specific use cases. Therefore, expanding them is usually not as difficult as expanding the coreUI components.
Conclusion
In conclusion, a well-organized and modern structure for a React project is essential for its success. By creating a solid plan and following best practices, we can ensure that our project is scalable, maintainable, and efficient. Good project structuring involves organizing the codebase and defining a clear hierarchy of files and directories, which makes it easier to manage, maintain, and navigate the code.
In this article, we have introduced the concept of project structuring, its importance, and the different aspects of structuring a React project. We have also provided a Core UI based Structure
that utilizes a layered approach to structure each page with multiple independent layers.
By following best practices for project structuring and leveraging our knowledge and expertise as software engineers, we can create maintainable, scalable, and efficient React projects that are easy to manage, modify, and update.
Subscribe to my newsletter
Read articles from Mahmoud Bassam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by