When it comes to developing React applications, in terms of code architecture, small projects are often more flexible than large ones. There is nothing wrong with creating such projects using practical recommendations aimed at larger applications. But all this, in the case of small projects, may simply be unnecessary. The smaller the application, the more “condescending” it is to use simple solutions in it, possibly non-optimal, but not time-consuming to implement them.

Despite this, I would like to note that some of the recommendations that will be given in this material are aimed at React-applications of any scale.
If you have never created a production application, then this article can help you prepare for the development of large-scale solutions. Something like this may well be one of your next projects. The worst thing that can happen to a programmer is when he is working on a project and understands that he needs to refactor large amounts of code to improve the scalability and maintainability of the application. Everything looks even worse if there were no unit tests in the project before refactoring.
')
The author of this material asks the reader to take his word for it. He has been in such situations. So, he got a few tasks that had to be solved for a certain time. At first, he thought he did everything perfectly. The source of such thoughts was that his web application, after making changes, continued to work, and at the same time continued to work quickly. He knew how to use Redux, how to make normal interactions between user interface components. It seemed to him that he deeply understood the concepts of reducer and action. He felt invulnerable.
But then the future crept up.
After a couple of months of work on the application, more than 15 new features were added to it. After that, the project went out of control. The code that used the Redux library has become very hard to maintain. Why did it happen so? Didn’t it at first seem like the project had a long and cloudless life?
The author of the article said that, asking such questions, he realized that he had himself planted a time bomb in the project.
The Redux library, if properly used in large projects, helps, as such projects grow, to keep their code in a supported state.
Here will be given 11 tips for those who want to develop scalable React-applications using Redux.
1. Do not place the code of actions and constants in one place
You might have come across some Redux guides in which constants and all actions are placed in the same place. However, this approach, as the application grows, can quickly lead to problems. Constants must be stored separately, for example, in
./src/constants
. As a result, to search for constants you will have to look only into one folder, and not into several.
In addition, it seems completely normal to create separate files that store actions. Such files encapsulate actions directly related to each other. Actions in the same file, for example, may have similarities in terms of what and how they are used.
Suppose you develop an arcade or role-playing game and create the classes
warrior
(warrior),
sorceress
(sorceress) and
archer
(archer). In such a situation, it is possible to achieve a high level of code maintenance by organizing actions as follows:
src/actions/warrior.js src/actions/sorceress.js src/actions/archer.js
It will be much worse if everything gets into one file:
src/actions/classes.js
If the application becomes very large, then perhaps it would be even better to use approximately the following structure of breaking the code into files:
src/actions/warrior/skills.js src/actions/sorceress/skills.js src/actions/archer/skills.js
Only a small fragment of this structure is shown here. If you think more broadly and consistently use this approach, you will end up with approximately the following set of files:
src/actions/warrior/skills.js src/actions/warrior/quests.js src/actions/warrior/equipping.js src/actions/sorceress/skills.js src/actions/sorceress/quests.js src/actions/sorceress/equipping.js src/actions/archer/skills.js src/actions/archer/quests.js src/actions/archer/equipping.js
Here is what an action from the
src/actions/sorceress/skills
file for a
sorceress
object might look like:
import { CAST_FIRE_TORNADO, CAST_LIGHTNING_BOLT } from '../constants/sorceress' export const castFireTornado = (target) => ({ type: CAST_FIRE_TORNADO, target, }) export const castLightningBolt = (target) => ({ type: CAST_LIGHTNING_BOLT, target, })
Here is the contents of the
src/actions/sorceress/equipping
:
import * as consts from '../constants/sorceress' export const equipStaff = (staff, enhancements) => {...} export const removeStaff = (staff) => {...} export const upgradeStaff = (slot, enhancements) => { return (dispatch, getState, { api }) => {
The reason why we organize the code in this way is that new features are constantly added to projects. This means that we need to be ready for their appearance and at the same time strive to ensure that files are not overloaded with code.
At the very beginning of the project, this may seem redundant. But the more the project becomes, the stronger the strength of this approach will be felt.
2. Do not place the reducer code in one place.
When I see that the code of my reducers turn into something like the one shown below, I understand that I need to change something.
const equipmentReducers = (state, action) => { switch (action.type) { case consts.UPGRADING_STAFF: return { ...state, classes: { ...state.classes, sorceress: { ...state.classes.sorceress, equipment: { ...state.classes.sorceress.equipment, isUpgrading: action.slot, }, }, }, } case consts.UPGRADED_STAFF: return { ...state, classes: { ...state.classes, sorceress: { ...state.classes.sorceress, equipment: { ...state.classes.sorceress.equipment, isUpgrading: null, current: { ...state.classes.sorceress.equipment.current, [action.slot]: action.staff, }, }, }, }, } case consts.UPGRADE_STAFF_FAILED: return { ...state, classes: { ...state.classes, sorceress: { ...state.classes.sorceress, equipment: { ...state.classes.sorceress.equipment, isUpgrading: null, }, }, }, } default: return state } }
Such a code, no doubt, can very quickly lead to a lot of confusion. Therefore, it is best to maintain the structure of work with the state in the simplest possible way, striving for the minimum level of their nesting. You can, instead, try to resort to the composition of reducer.
A useful technique in working with reduction gears can be the creation of a higher-order reduction gear that generates other reducers.
Here you can read more about this.
3. Use informative variable names.
Naming variables, at first glance, may seem like an elementary task. But in fact, this task may be one of the most difficult.
The selection of variable names, in general, is related to practical recommendations for writing clean code. The reason why there is such a thing as “variable name” in general is that this aspect of code development plays a very important role in practice. Unsuccessful selection of variable names is a sure way to harm yourself and your team members in the future.
Have you ever tried to edit someone else's code and did you encounter any difficulties in understanding what this code does? Have you ever run someone else's program and find out that it does not work as expected?
I would argue to prove that in such cases you encountered the so-called “dirty code”.
If you encounter such code in large applications, then this is just a nightmare. This, unfortunately, happens quite often.
Here is one case from life. I edited the React hook code from one application and at that moment they sent me a task. It consisted in realizing in the application the possibility of displaying additional information about doctors. This information should have been shown to a patient who clicks on the doctor's avatar. It was necessary to take it from the table, it should have been sent to the client after processing the next request to the server.
The task was simple, the main problem I encountered was that I had to spend too much time trying to find exactly where I needed in the project code.
I looked in the code for the words
info
,
dataToSend
,
dataObject
, and for others that, in my view, are associated with data received from the server. After 5-10 minutes I managed to find the code responsible for working with the data I needed. The object in which they found themselves was named
paymentObject
. In my view, a payment-related object may contain something like a CVV code, credit card number, a payer's postal code, and other similar information. In the object I discovered there were 11 properties. Only three of them were related to payments: the method of payment, the identifier of the payment profile and the list of coupon codes.
It did not improve the situation and the fact that I had to make changes to this object that were required to solve the task before me.
In short, it is recommended to refrain from using obscure names for functions and variables. Here is a sample code in which the name of the function
notify
does not reveal its meaning:
import React from 'react' class App extends React.Component { state = { data: null }
4. Do not change data structures or types in already configured application data streams.
One of the biggest mistakes I ever made was a change in the data structure in an already configured application data stream. The new data structure would bring a huge performance boost, as it used fast data retrieval techniques in objects stored in memory, instead of iterating over arrays. But it was too late.
Please do not do this. Perhaps, only someone who absolutely knows about which parts of the application this can affect can afford something like that.
What are the consequences of such a step? For example, if something was first an array, and then became an object, it could disrupt many parts of the application. I made a huge mistake in believing that I could remember all the places in the code that could be affected by a change in the presentation of structured data. However, in such cases, there is always some piece of code that is affected by the change, and which no one remembers.
5. Use snippets
I used to be a fan of the Atom editor, but switched to VS Code due to the fact that this editor, in comparison with Atom, was incredibly fast. And he, at his speed, supports a huge number of very different possibilities.
If you also use VS Code - I recommend installing the
Project Snippets extension . This extension allows the programmer to create his own snippets for each workspace used in a project. This extension works in the same way as the Use Snippets mechanism built into VS Code. The difference is that when working with Project Snippets in a project, they create a
.vscode/snippets/
folder. It looks like the following figure.
Contents of the .vscode / snippets / folder6. Create modular, end-to-end and integration tests
As the size of the application grows, it becomes more and more terrible for the programmer to edit code that is not covered by tests. For example, it may happen that someone edited a code stored in
src/x/y/z/
and decided to send it to production. If, in this case, the changes made affect those parts of the project that the programmer did not think about, everything could end in an error that the real user will encounter. If there are tests in the project, the programmer finds out about the error long before the code gets into production.
7. Brainstorm
Programmers, in the course of introducing new features into projects, often refuse to brainstorm. This is due to the fact that this activity is not related to writing code. Especially often this happens when the task is given quite a bit of time.
And why, by the way, do brainstorming work during application development at all?
The fact is that the more complex an application becomes, the more attention programmers have to pay to its individual parts. Brainstorming helps to reduce the time needed to refactor code. After they are held, the programmer turns out to be armed with the knowledge of what can go wrong during the finalization of the project. Often, programmers, engaged in the development of the application, do not even bother to think at least a little bit about how to do everything in the best way.
That is why brainstorming is very important. During such an event, the programmer can consider the code architecture, think about how to make the necessary changes to the program, trace the life cycle of these changes, create a strategy for working with them. It is not necessary to start the habit of keeping all plans solely in one’s own head. So do programmers who are overly confident. But to remember everything is simply impossible. And, once something is done wrong, problems will appear one after another. This is the principle of domino in action.
Brainstorming is also useful in teams. For example, if someone encounters a problem during the course of work, he may turn to brainstorming materials, since the problem he has had may well have been thought out. Notes that are made during brainstorming may well play the role of a plan for solving the problem. This plan allows you to clearly assess the amount of work performed.
8. Create application layouts
If you are going to start developing the application, you need to decide how it will look and how users will interact with it. This means that you will need to create a mock app. For this you can use a variety of tools.
Moqups is one of the tools for creating application layouts that I often hear about. This is a fast tool created by HTML5 and JavaScript tools and does not impose special system requirements.
Creating a layout of the application greatly simplifies and speeds up the development process. The layout gives the developer information about the relationship of individual parts of the application, and about what kind of data will be displayed on its pages.
9. Plan data flow in applications.
Almost every component of your application will be associated with some data. Some components will use their own data sources, but most components receive data from entities above them in the component hierarchy. For those parts of the application in which the same data is shared by several components, it is useful to provide some kind of centralized storage of information located at the top level of the hierarchy. It is in such situations that the
Redux library can provide invaluable assistance to the developer.
In the course of working on an application, I recommend drawing up a chart showing the ways in which data moves in this application. This will help in creating a clear model of the application, moreover, we are talking about the code, and the programmer's perception of the application. Such a model will help, moreover, in the creation of reducer.
10. Use data access functions.
As the size of an application grows, so does the number of its components. And when the number of components grows, the same thing happens with the frequency of use of selectors (react-redux ^ v7.1) or
mapStateToProps
. Suppose you find that your components or hooks often refer to state fragments in different parts of an application using a construct like
useSelector((state) => state.app.user.profile.demographics.languages.main)
. If so, it means that you need to think about creating data access functions. Files with such functions should be stored in a public place from which components and hooks can import them. Similar functions can be filters, parsers, or any other functions for data transformation.
Here are some examples.
For example, the following code may be present in
src/accessors
:
export const getMainLanguages = (state) => state.app.user.profile.demographics.languages.main
Here is the version using
connect
, which can be located along the path
src/components/ViewUserLanguages
:
import React from 'react' import { connect } from 'react-redux' import { getMainLanguages } from '../accessors' const ViewUserLanguages = ({ mainLanguages }) => ( <div> <h1>Good Morning.</h1> <small>Here are your main languages:</small> <hr /> {mainLanguages.map((lang) => ( <div>{lang}</div> ))} </div> ) export default connect((state) => ({ mainLanguages: getMainLanguages(state), }))(ViewUserLanguages)
Here is the version in which
useSelector
is used, located at
src/components/ViewUserLanguages
:
import React from 'react' import { useSelector } from 'react-redux' import { getMainLanguages } from '../accessors' const ViewUserLanguages = ({ mainLanguages }) => { const mainLanguages = useSelector(getMainLanguages) return ( <div> <h1>Good Morning.</h1> <small>Here are your main languages:</small> <hr /> {mainLanguages.map((lang) => ( <div>{lang}</div> ))} </div> ) } export default ViewUserLanguages
In addition, strive to ensure that such functions would be immutable, devoid of side effects. You can find out why I give such a recommendation
here .
11. Control the flow of data in properties using restructuring and spread syntax
What are the advantages of using the
props.something
construct over the
something
construct?
Here's what it looks like without using destructuring:
const Display = (props) => <div>{props.something}</div>
Here is the same thing, but already using restructuring:
const Display = ({ something }) => <div>{something}</div>
Using restructuring improves code readability. But his positive influence on the project is not limited to this. By applying destructuring, the programmer is forced to make decisions about what exactly the component receives, and what exactly it outputs. This saves those who have to edit someone else's code from having to look through every line of the
render
method in the search for all the properties that the component uses.
In addition, this approach provides a useful opportunity to set default property values. This is done at the very beginning of the component code and eliminates the need to write additional code in the body of the component:
const Display = ({ something = 'apple' }) => <div>{something}</div>
You may have seen something like this before:
const Display = (props) => ( <Agenda {...props}> {' '} // Agenda <h2><font color="#3AC1EF">Today is {props.date}</font></h2> <hr /> <div> <h3><font color="#3AC1EF">â–ŤHere your list of todos:</font></h3> {props.children} </div> </Agenda> )
Such constructions are not easy to read, but this is not their only problem. So, there is an error here. If the application also outputs child components,
props.children
displayed on the screen twice. If the project is being worked on as a team and the team members are not attentive enough, the likelihood of such errors is rather high.
If you instead destructurize properties, the component code will be clearer, and the probability of errors will decrease:
const Display = ({ children, date, ...props }) => ( <Agenda {...props}> {' '} // Agenda <h2><font color="#3AC1EF">Today is {date}</font></h2> <hr /> <div> <h3><font color="#3AC1EF">â–ŤHere your list of todos:</font></h3> {children} </div> </Agenda> )
Results
In this article, we reviewed 12 recommendations for those developing React applications using Redux. We hope you found something here that is useful to you.
Dear readers! What tips would you add to what is shown in this article?

