Best React Practices for Building Better ReactJS Applications
If you have researched how to best structure the folder structure of a React application, you have undoubtedly encountered a wide variety of opinions. While this topic may be somewhat subjective, there are some recommendations that are best to follow. So, whether you are a beginner in React development or an old hand with a different opinion, there is something here for everyone.
#1 Best Practices for React Folder Structure
Group Folders by Feature
If you know which features will be included in your ReactJS application, you can easily create a folder structure based on these key features. This will create a very clean directory structure for the application and let you know exactly which code goes where. It also makes debugging and collaboration much easier.
Avoid Deep Nesting
When creating a folder structure, you should not allow too many nested folders. If you find this is the case early on, you may need to reconsider the folder hierarchy and break it into smaller parts. One of the main problems with deep nesting is that it becomes difficult to write relative imports between deeply nested folders or update folders when moving files.
Overthinking Is Your Enemy
When starting to create a folder structure for a project, don't overthink it. If you are having trouble getting started (because your mind is going in circles), just dump all initial files into the document root. As you work on the project, you will understand what the folder structure should be. Once that becomes clear, create folders and move files into them. You can even start with a src folder inside the main project folder and then move files out of that folder as things become obvious. Just don't leave the project in such a single-folder structure, because as the project grows, confusion can arise.
You can simply start with one base folder (src) and two subfolders (App and List) to house application components, which might look something like this:
- src/
--- App/
----- index.js
----- App.js
----- App.test.js
----- App.style.css
--- List/
----- index.js
----- List.js
----- List.test.js
----- List.style.css
Separation of Components and Utilities
You can also structure folders by separating components from hooks. Such a folder structure might look like this:
- src/
--- components/
----- App/
------- index.js
------- component.js
------- test.js
------- style.css
----- List/
------- index.js
------- component.js
------- test.js
------- style.css
--- hooks/
----- useClickOutside/
------- index.js
------- hook.js
------- test.js
----- useScrollDetect/
------- index.js
------- hook.js
------- test.js
#2 Best Practices for Using React Testing Library
Test Behavior, Not Implementation
There is a big difference between behavior and implementation. The difference is simple:
When testing behavior, you don't care how you arrived at the answer, only that the answer is correct under certain circumstances.
When testing implementation, you don't care about the answer, only that you are doing a certain thing while figuring it out.
Testing behavior makes it more likely to arrive at a viable, repeatable conclusion, so testing this way is the best option.
Use Custom Render Methods for Dependency Mocks
Using custom render methods to mock dependencies allows you to avoid performing the same setup for every test. As the project grows, you certainly don't want to repeat the same setup for each test. With custom render methods, you can also improve the repeatability and maintainability of trial tests.
Test Functionality, Not Implementation
If you focus on testing implementation details, you will be testing how your code is written, not what it does. With this approach, if you change the code, your tests will fail even if the functionality of the code hasn't changed. If you test functionality, the tests will remain functional even if you change the code implementation.
Write Tests That Don't Break When the UI Changes
Your user interface will change. It's inevitable. When creating tests, you should keep this in mind and write tests so they don't break when the UI changes. Be sure to create tests that focus on the behavior of the component, not its implementation. For example, instead of checking that the menu uses the correct CSS class, you should check that clicking on the menu yields the expected results.
Don't Test What React Already Tests
The React library is already very well tested. When using components from this library, you don't need to test them. Instead, focus your tests on the components and code you write. Avoid such redundancies as they only waste time.
Use Helpers to Simulate Events
React includes a number of helpers for simulating events, such as change(), click(), and keydown(), which simulate events without actually triggering them. Be sure to use these helpers instead of fireEvent, since fireEvent actually dispatches the event to the DOM. Given that a DOM node may not be able to handle certain types of events, this can lead to problems.
Always Use Act() for Testing Async Events
Similarly, you should use the act() function to test asynchronous events. When testing async events, the React Testing Library will wait for tasks to complete before running any assertions. The problem is that this only works if the async tasks are triggered by an event handler. To avoid this issue, wrap the async task in an act() call, and then the React Testing Library will wait for all async tasks to complete before running assertions.
Assert Only One Thing in Each Test
This is very important. If you assert multiple things in one test and one of them fails, the test will fail and you won't know which assertion caused the problem. Instead, focus the test on a single assertion so that if the test fails, you know exactly which assertion is problematic.
Keep Tests Simple and Focused
Another key aspect of testing is simplicity. When creating overly complex tests, debugging issues becomes more difficult. This is especially true as the project grows in complexity.
#3 Best Practices for React Security
Use Default XSS Protection with Data Binding
Although React is quite secure, it can still be vulnerable to things like cross-site scripting (XSS). For example, for data binding by default, you should always use curly braces. This ensures that React automatically escapes values to protect against XSS attacks. Here is an example.
Instead of this:
<form action={data}>
Use this:
<div>{data}</div>
Avoid URL-Based Script Injection
By using the javascript: protocol, URLs can contain dynamic content, which can lead to URL-based script injection. To avoid this, use a custom URL parser and then match the parsed protocol property against an allowlist.

Use a Sanitization Library
In ReactJS, HTML can be inserted directly into rendered DOM nodes using the dangerouslySetInnerHTML function, which allows developers to directly insert HTML content inside an HTML element in a React application. When inserting content this way, it must be sanitized using a sanitization library such as DOMPurify. Use it for any values before they are placed into the dangerouslySetInnerHTML prop. An example of this looks like this:
import purify from "dompurify";
<div dangerouslySetInnerHTML={{ __html:purify.sanitize(data) }} />
Check for Known Vulnerabilities in Dependencies
If you are using third-party components, make sure to thoroughly check them for vulnerabilities. Since these components are pre-built by another programmer (or team of programmers), you shouldn't just assume they have been checked for issues. If you are unsure about a component's security, check it yourself before implementing it.
Avoid JSON Injection Attacks
JSON data is often transferred with server-side rendering of pages in React. When doing this, you should always escape the < character with any safe value. This helps prevent injection attacks.
Here is an example illustrating this practice:
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace( /</g, '\\u003c')}.
Use Safe Versions of React
Always ensure you are using the latest version of React. Otherwise, you risk using a version that contains vulnerabilities, some of which could be critical. This applies to both react and react-dom.
Use a Linter
There are tools called linters that help detect any security issues in your codebase. One such linter is the ESLint React Security config tool, which is open source and freely available. Linters should be used not only for your own code but also for library code. It's always better to be safe than sorry.
#4 Best Practices for Authentication in React
Use a Library
When authentication is required in an application, it's best to use a library. Remember, authentication is serious business and can involve many risks. Instead of writing your own code that may have security issues, turn to a ready-made library for this functionality. There are many React authentication libraries that can perform various functions. Find one that meets your needs and use it.
Don't Store Sensitive Data in Local Storage
At some point, you will need to store sensitive data, which may include user/client data. If you store data in local storage (client-side), any user of the device (or even third-party applications) can access it, which can lead to security issues. Instead of storing data in local storage, use a more secure option such as session storage. Even better, don't store sensitive information on the local device... period.
Protect API Endpoints
One security aspect that some overlook is API endpoints. The problem is that if an attacker manages to gain access to one (or more) endpoints, they will have access to everything, including client data. One method of protecting API endpoints is to use JSON Web Tokens (JWT), which are a standard method for securely transmitting information. Before transmitting information, the token must be verified on your server.
If using JWT is not possible, you should at least use HTTPS to encrypt data. In that case, if an attacker gains access to the API, the data will at least be encrypted and cannot be easily read. Using HTTPS also provides the additional benefit of protection against man-in-the-middle attacks, which is another way attackers gain access to your data.
Encrypt Passwords and Other Sensitive Information
Speaking of encryption: never leave passwords and other sensitive information in plain text. In that case, your application or service will be open to attacks. Even worse if your application or service is used by consumers or clients who store passwords and other information in it. If this information is not encrypted, hackers can access and read it. Leaving client/consumer data in plain text is never acceptable.
Implement Login Attempt Rate Limiting
If you are not using login attempt rate limiting, you are leaving your application or service open to brute-force attacks. In that case, it's only a matter of time before a hacker can break into the application and steal the data inside. You should limit the number of login attempts, say five failed attempts within a certain time period. Once that limit is exceeded, the account should be locked for a certain time. This gives the system a chance to detect and report failed attempts. Without rate limiting, attackers can keep trying to log in until they succeed and gain access to your application or service.
Use 2FA or MFA for Extra Security
Many users are reluctant to use two-factor (2FA) or multi-factor (MFA) authentication for login. Once they finally understand why it's necessary (and that it's not as much of an inconvenience as they thought), users agree to these additional security layers. Instead of making 2FA or MFA optional, consider making them mandatory. Although 2FA and MFA are not perfect, they certainly make it harder for attackers to gain access to your application or service. Any additional protection you can add to an application, service, or website should be mandatory.
Don't Use Email as Username
Speaking of account security, consider banning the use of email addresses as usernames. Why? Using email addresses as usernames is very common, meaning hackers will have less trouble guessing one of the login criteria. Instead of using email addresses as usernames, consider forcing users to create unique usernames, which makes authentication hacking significantly more difficult and makes your application much more secure.
Use Passwordless Authentication
One of the most reliable authentication methods is passwordless authentication. Instead of a regular password to log in, the user receives a one-time code sent to their phone number and then uses it to log in. Even more secure is using a code from an authenticator app, such as Authy or Google Authenticator. Since such codes are used only once, a hacker cannot guess the password—because it simply doesn't exist.
The only disadvantage of passwordless authentication is sending codes to the phone via SMS. These codes can be intercepted by hackers, making it much easier to break into an account. Of course, if a user works with a unique username (rather than an email address), even if the hacker intercepts the code, they still have to know the username associated with the account to gain access.
Log Out Inactive Users
One feature of mobile and web applications is that users work with them for a while, then the app bores them or they stop using it. In the case of mobile apps, these forgotten apps tend to remain installed and accounts remain registered. This can be a security issue. To get around this, consider building into the application/service a feature that automatically logs users out after a specified period of inactivity.
When automatically logged out of the application, a third-party user cannot unlock the phone and open the app without first authenticating. Yes, this can be a slight inconvenience for users who do not use the application regularly, but such convenience is not worth a compromised account. If one user's account is hacked, who knows if the attacker might gain access to other accounts, API endpoints, or the hosting server.
Best Practices ReactJS: Conclusion
ReactJS is widely used worldwide for building interactive interfaces for both web applications and mobile devices. Given the widespread use of such applications, developers must constantly follow best practices to prevent hackers from gaining access to sensitive data, which can lead to bigger troubles than you might imagine.
With a little caution, such a disaster can be avoided. In the end, your clients and customers will trust your application and your company. Such trust cannot be bought or sold, so it should be considered an invaluable asset.
See also: Software Development, Web Application Development: Resources, Best Practices and How to Do It.


