# Titles
### 1. React Project Outgrowing Expectations? Learn These Basic Principles to Manage Better

### 2. React Project Exploding? Master Simplicity Fast with These Simple Fundamentals

### 3. My React Product is Exploding! I am Simplifying with These Simple Fundamentals

## Consequences of Poor Code Maintenance
Imagine building a product with great potential, but watching it progress slowely due to messy code and disorganized thinking.
We are a small team, building a product called Hexmos Feedback, Feedback keeps teams motivated and engaged through recognition and continous feedback.
We have attendance management as a part of Hexmos Feedback.
Hexmos Feedback goes beyond simple attendance – it helps identify commitment employees who are present but may not be fully invested in the organization's goals.
We opted for React, known for its ease of use and scalability, as the foundation for our product's frontend.
We were focused on getting features out the door, screens looking good, and functionality working. In that initial sprint, coding standards wasn't exactly a priority.
Looking back, we just threw components wherever they fit, integrated APIs directly into component files (except for that base URLs!), and cobbled together structures that barely held things together.
Again, for a small team focused on a bare-bones MVP, it wasn't the worst approach.
**Growing Pains: The Need for Scalability**
But then, things changed. As our capabilities matured a little bit, we started training students from various colleges, and we brought on some committed interns to help us scale our team.
That's when the cracks began to show. Our once "functional" codebase became difficult for these newbies to navigate.
Simple bug fixes turned into hour-long hunts just to find the relevant code.
It was a wake-up call. Our codebase was hindering progress and killing developer morale. That's when I knew we had to make a change.
## Learning From Our Mistakes: Embracing Best Practices
It was time to learn some best practices, clean up the mess, and build a codebase that was scalable, maintainable, and wouldn't make our interns tear their hair out.
### Organizing for Scalability
It took me more than two hours to organize the components, fix the import errors, and things got slightly better.
I understood the importance of a well-structured codebase for easy scalability and maintenance.
Most of the code should be organized within feature folders.
```markmap
- app
- app.tsx
- src
- features
- Feature1
- Components
- Jsx files
- Redux
- actionCreator.ts
- reducer.ts
- state.ts
- Feature2
- Components
- Jsx files
- Redux
- actionCreator.ts
- reducer.ts
- state.ts
- components
- stores
- utils
- assets
- test
```
Each feature folder should contain code specific to that feature, keeping things neatly separated.

This approach helped prevent mixing feature-related code with shared components, making it simpler to manage and maintain the codebase compared to having many files in a flat folder structure.
### How Redux Simplified API Integration
Remember that initial focus on getting features out the door? While it worked for our simple MVP, things got hairy as we added more features.
Our initial approach of making API calls directly from component files became a burden.
As the project grew, managing loaders, errors, and unnecessary API calls became a major headache.
Features were getting more complicated, and our codebase was becoming a tangled mess.
Component file's lines increased, business logic (API calls, data transformation) and UI logic (displaying components, handling user interactions) got tangled, and maintaining the codebase and complexity to handle loaders, errors, etc became a nightmare.
As our project matured and the challenges of managing state grew, I knew it was time to implement a better approach.
I started using [Redux](https://www.npmjs.com/package/@reduxjs/toolkit) for API calls, a state management library for JavaScript applications, along with thunks (like createSlice, createAsyncThunk from @reduxjs/toolkit) to handle this problem.
This is a strong approach for complex applications as Redux acts as a central hub for your application's state, promoting separation of concerns and scalability.
#### Here's why Redux was a game-changer:
##### 1. Centralized State Management:
Redux keeps all your application's state in one central location. This makes it easier to track changes, manage loading and error states, in any component. No more hunting through a maze of component files!
##### 2. Separation of Concerns:
Thunks and slices (Redux's way of organizing reducers and actions) allow you to cleanly separate your business logic (API calls, data transformation) from your UI logic (displaying components, handling user interactions). This makes the code more readable, maintainable, and easier to test.
##### 3. Consistency:
Redux enforces a consistent way to handle API calls and manage state. This leads to cleaner, more maintainable code across your entire project.
##### 4. Caching for Efficiency:
Redux can help with caching and memoization of data, reducing unnecessary API calls and improving performance.
We'll dive into a concrete example in the next section to show you how Redux and thunks can be implemented to tame the API beast in your own React projects.
### How to reduce burden using Redux
Here is how I got started with the conversion.
Considering you have already installed [reduxjs/toolkit](https://www.npmjs.com/package/@reduxjs/toolkit).
Below image is how I have organized the files for redux in my streak feature.

#### Async Actions with Redux Thunk (`actionCreator.ts`):
First create a file `actionCreator.js` and add a thunk for the API call of your component.
This file defines an asynchronous thunk action creator named `fetchIndividualStreak` using `createAsyncThunk` from `@reduxjs/toolkit`.
Thunks are middleware functions that allow us to perform asynchronous operations (like API calls) within Redux actions.
It handles both successful and unsuccessful responses:
- On success, it returns the response data.
- On failure, it rejects the promise with an error message.
```typescript!
// actionCreator.ts
import { createAsyncThunk } from "@reduxjs/toolkit";
export const fetchIndividualStreak = createAsyncThunk(
"streak/fetchIndividualStreak",
async (memberId, { rejectWithValue }) => {
try {
const data = {
member_id: memberId,
};
const response = await apiWrapper({
url: `${urls.FB_BACKEND}/v3/member/streak`,
method: "POST",
body: JSON.stringify(data),
});
return response;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
```
#### Centralized State (`reducer.ts`):
This file defines a slice using `createSlice` from `@reduxjs/toolkit`.
A slice groups related reducers and an initial state for a specific feature (streak in this case).
The initial state includes properties for:
- `loading`: indicates if data is being fetched
- `data`: holds the fetched streak information
- `error`: stores any errors encountered during the API call
```typescript!
// reducer.ts
import { createSlice } from "@reduxjs/toolkit";
import { fetchIndividualStreak } from "./actionCreator";
const streakSlice = createSlice({
name: "streak",
initialState: {
loading: false,
data: null,
error: null,
},
reducers: ...,
extraReducers: ...,
});
```
##### State Flow During Execution
###### Initial Render: Before API stats executing
When the component mounts, the streak slice's initial state is used.
This means `loading` is initially false, `data` is null, and `error` is null.
The UI won't display a loader or any streak information at this point.
###### Fetching Data: Triggering API fetching
The UI component dispatches the `fetchIndividualStreak` action using `useDispatch`.
This triggers the asynchronous thunk functionality within the action creator.
###### Pending State: API is already triggered
While the API call is in progress, the `fetchIndividualStreak.pending` case in the reducer is triggered.
This sets the loading state to true and clears any existing `error`.
The UI can now display a *loader* to indicate that data is being *fetched*.
```typescript!
// reducer.ts
const streakSlice = createSlice({
extraReducers: (builder) => {
/*----------- Handle Pending State ------------------------*/
builder.addCase(fetchIndividualStreak.pending, (state) => {
state.loading = true;
state.error = null;
})
},
});
```
###### Success (Fulfilled State): API success
If the API call is successful, the `fetchIndividualStreak.fulfilled` case is triggered.
This sets the `loading` state back to false and updates the `data` state with the fetched streak information.
The component's `useEffect` hook that monitors the `data` state will then trigger a re-render, and the UI can use the updated `data` to display the necessary information.
```typescript!
// reducer.ts
const streakSlice = createSlice({
extraReducers: (builder) => {
/*----------- Handle Fullfiled State ------------------------*/
builder.addCase(fetchIndividualStreak.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
},
});
```
###### Failure (Rejected State):
If the API call fails, the `fetchIndividualStreak.rejected` case is triggered.
This sets the `loading` state back to false and updates the `error` state with the error message from the API.
The UI can conditionally render an error message based on the presence of an error in the state.
```typescript!
// reducer.ts
const streakSlice = createSlice({
name: "streak",
initialState: ...initialState,
reducers: {
setStreakPackage: (state, action) => {
state.data = action.payload;
},
},
extraReducers: (builder) => {
/*----------- Handle Pending State -------------------------*/
builder.addCase(fetchIndividualStreak.pending, (state) => {
state.loading = true;
state.error = null;
})
/*----------- Handle Fullfiled State ------------------------*/
builder.addCase(fetchIndividualStreak.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
/*----------- Handle Rejected State ------------------------*/
builder.addCase(fetchIndividualStreak.rejected, (state, action) => {
state.loading = false;
state.error = action.payload
});
},
});
export const { setStreakPackage } = streakSlice.actions;
export default streakSlice.reducer;
```
By managing the state transitions within Redux reducers, we can efficiently communicate loading states, errors, and successful data retrieval to the React components, enabling them to update the UI accordingly. This centralized approach to state management and API interactions leads to a more responsive and user-friendly experience.
#### Connecting the Dots in the Component (IndividualStreak.tsx):
This component displays the user's streak information on a calendar.
It uses `useDispatch` from React-Redux to dispatch actions and useSelector to access the Redux state.
The initial `useEffect` hook triggers the `fetchIndividualStreak` action to retrieve streak data for the specified member ID.
```typescript!
// IndividualStreak.tsx
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchIndividualStreak } from "./redux/actionCreator";
import { setStreakPackage } from "stores/slices";
function IndividualStreak({ memberId }) {
const dispatch = useDispatch();
const { loading, data: streakData, error } = useSelector((state: any) => state.streak);
useEffect(() => {
dispatch(fetchIndividualStreak(memberId));
}, [memberId, dispatch]);
```
A second `useEffect` hook conditionally dispatches the `setStreakPackage` reducer only if data is successfully fetched and has feedback information.
```typescript!
useEffect(() => {
if (streakData && streakData.feedback_data && streakData.feedback_data.length > 0) {
dispatch(setStreakPackage(streakData.feedback_data));
}
}, [streakData, dispatch]);
```
The component conditionally renders a loading bar, error message, or the calendar component based on the state (`loading`, `error`, and `streakData`).
```typescript!
// IndividualStreak.tsx
import Box from "@mui/material/Box";
import LinearProgress from "@mui/material/LinearProgress";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import CalendarWrap from "../CalendarWrap";
import { fetchIndividualStreak } from "./redux/actionCreator";
import { setStreakPackage } from "stores/slices";
import Alert from "@mui/material/Alert";
function IndividualStreak({ memberId }) {
const dispatch = useDispatch();
const { loading, data: streakData, error } = useSelector((state: any) => state.streak);
useEffect(() => {
dispatch(fetchIndividualStreak(memberId));
}, [memberId, dispatch]);
useEffect(() => {
if (streakData && streakData.feedback_data && streakData.feedback_data.length > 0) {
dispatch(setStreakPackage(streakData.feedback_data));
}
}, [streakData, dispatch]);
return (
<div>
{loading && (
<Box sx={{ width: "100%" }}>
<LinearProgress />
</Box>
)}{" "}
{error && <Alert severity="error">{error}</Alert>}
{streakData && (
<div style={{ width: "50%" }}>
<CalendarWrap
startDate={moment().subtract(12, "month")}
endDate={moment()}
streakData={streakData.feedback_data}
/>
</div>
)}
</div>
);
}
export default IndividualStreak;
```
This simplified example showcases the power of Redux for state management and API interactions in React applications.
Centralizing state and handling API calls within Redux leads to several benefits:
#### Improved Separation of Concerns:
Business logic (API calls, data transformation) is separated from UI logic (displaying components, handling user interactions). This makes code cleaner and easier to maintain.
#### Enhanced Maintainability:
Redux actions and reducers are easier to test and manage compared to components with embedded API logic.
#### Scalability:
The Redux approach scales well for handling complex features like pagination, searching, and filtering, as these functionalities can leverage the centralized state management.
By offloading the responsibility of managing loaders, errors, and API success/failure from components to Redux, we achieve a cleaner and more maintainable codebase. This approach lays the foundation for building robust and scalable React applications.
### The Final Touches
As we wrapped our heads around Redux and implemented a solid code structure, we knew there was still room for improvement.
One area that constantly caused friction during code reviews was formatting inconsistencies.
Interns had different preferences for code formatters and styling configurations, leading to a sea of unnecessary changes cluttering the review process.
This is where pre-commit hooks entered the scene. We implemented Husky, a popular tool that allows us to run scripts before a commit is made.
This ensures that everyone's code adheres to the same style guidelines, reducing noise in code reviews and preventing potential bugs that might arise from inconsistent formatting.
Want to see a deeper dive into implementing these practices with code examples? Check out the full implementation details in the following sections of [Auto-Formatting with Prettier, Husky & lint-staged on Every Git Commit](https://journal.hexmos.com/auto-formatting-with-prettier-husky-lint-staged-on-every-git-commit/) blog post.
## Conclusion
By embracing these practices, we've transformed our codebase from a tangled mess into a well-structured, maintainable, and scalable project.
This not only makes our lives easier but also ensures a smoother experience for our interns and future developers who join the team.
This concludes our exploration of the challenges and solutions we encountered while building our product.
I hope this narrative serves as a roadmap for those embarking on similar endeavors, helping you avoid the pitfalls we faced and build robust, maintainable applications.
# Title ideas:
1. **Learn How not to suffer while building your frontend in 10 mins**

---
2. **Way I Learned How Not to Suffer Building a Scalable Frontend**

---
3. **React Project Outgrowing Expectations? Learn These Principles to Manage It in 5 minutes**

4. **How I improved my React Codebase in One Month**

## Audiance
1. Frontend developers who is going to build large React projects.
2. Service companies who will build large React projects.
## Transformation
1. From normal react coding to scalable site focused coding.
---