BairesDev
  1. Blog
  2. Software Development
  3. React Best Practices for Better ReactJS Apps
Software Development

React Best Practices for Better ReactJS Apps

If your development teams work with ReactJS, you should consider these best practices.

BairesDev Editorial Team

By BairesDev Editorial Team

BairesDev is an award-winning nearshore software outsourcing company. Our 4,000+ engineers and specialists are well-versed in 100s of technologies.

14 min read

Featured image

If you’ve been doing your research into how best to strcuture your React app folder structure, you’ll no doubt have come across quiet a wide variety of opinions. Whilst this topic can be a little subjective, there are some guidelines that it’s best to follow. So whether you are completely new to React development or your are an old hand with a different opinion, there’s something here for everyone.

#1 React Folder Structure Best Practices

Group Folders by Features

If you know the features that will be included in your ReactJS application, you can easily create a folder structure based on those key features. By doing this, it’s possible to keep a very tidy directory structure for your app and you’ll know exactly what code goes where. This also makes it much easier to debug and collaborate.

Avoid Deep Nesting

As you create your folder structure, you don’t want to wind up with too many nested folders. If you find yourself doing that at the beginning, you might need to rethink your folder hierarchy and break things out more granularly. One of the biggest issues with deep nesting is that it becomes challenging to write relative imports between those deeply nested folders or to update those folders when files are moved around.

Overthinking Is Your Enemy

As you first begin the process of creating your folder structure for your project, don’t overthink it. If you find yourself struggling to get started (because your brain is thinking in circles) just dump all of the initial files into the document root. As the project continues, you’ll eventually see exactly what the folder structure should be. Once it becomes clear, create the folders and move the files into their new homes. You can even start with an src folder within your base project folder and then move files out of that folder as things become obvious. Just make sure not to leave your project in this single-folder structure, as things could get very confusing as the project grows.

You could simply start with a single base folder (src) and two subfolders (App and List) to house the components of the app, 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

Separate Components and Utilities

You could also structure your folders by separating components from hooks. This kind of 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 React Testing Library Best Practices

Test the Behavior, Not the Implementation

There’s a big difference between behavior and implementation. The difference is simple:

  • When testing for behavior, you don’t care how you arrive at the answer, just that the answer is correct under a certain set of circumstances.
  • When testing for implementation, you don’t care what the answer is, just that you do a specific thing as you figure it out.

Testing for behavior is more likely to arrive at a viable, repeatable conclusion, so testing in this manner is your best option.

Use Custom Render Methods for Dependency Mock-Ups

When you use a custom render method for mocking up dependencies, you can avoid having to go through this same setup every time you run a test. As your project scales, you certainly do not want to have to go through the same setup for every test. With custom render methods, your dry-run tests are also more repeatable and maintainable.

Test Functionality, Not Implementation

If you focus on testing implementation details, what you are doing is testing how your code is written, not what your code does. When going this route, if you alter your code, your tests will fail, even though the functionality of the code hasn’t changed. If, instead, you test functionality, those tests will remain viable, even if you alter the implementation of your code.

Write Tests That Don’t Break When the UI Changes

Your UI is going to change. That’s inevitable. When you create your tests, you should keep this in mind and write those tests such that they will not break should the UI change. Make sure to create tests that focus on the behavior of a component and not the implementation. For example, instead of testing that a menu uses the correct CSS class, you should test to make sure that clicking the menu gives the expected results.

Don’t Test That Which React Already Tests

The React library is already very well tested. When using components from the library, you shouldn’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 Event Simulation Helpers

React includes a number of simulation helpers — such as change(), click(), and keydown() — that simulate events without actually triggering them. Make sure to use these helpers, instead of the fireEvent, as that actually dispatches an event to the DOM. Given that the DOM node cannot handle certain types of events, it can lead to problems.

Always Use Act() For Testing Asynchronous Events

In the same vein, you should always use act() to test asynchronous events. With asynchronous events, the React Testing Library will wait for the tasks to complete before running any assertions. The problem with this is that it only works if the asynchronous tasks are started with an event handler. To avoid this issue, wrap your asynchronous task in an act() call so the React Testing Library will hold until all asynchronous tasks complete before running the assertions.

Only Assert One Thing per Test

This is critical. If you assert multiple things per test and one fails, the test fails and you won’t know which assertion is causing the problem. Instead, focus your testing on a single assertion so that, should the test fail, you know exactly which assertion is problematic.

Keep Your Tests Simple and Focused

Another key aspect of testing is to keep them simple. When you create tests that are overly complex, it becomes more challenging to debug the problems. This is especially true as your project becomes more complex.

#3 React Security Best Practices

Use Default Cross-Site Scripting Protection With Data Binding

Although React is fairly secure, it can still be vulnerable to things like cross-site scripting (XSS). For example, you should always use curly braces for default data binding. Doing this ensures that React will automatically escape values to protect against XSS attacks. Here’s an example.

Instead of:

<form action={data}>

Use this:

<div>{data}</div>

Avoid Url-Based Script Injection

Using the javascript: protocol, URLs can contain dynamic content that can lead to URL-based script injection. To avoid this, use a native URL parsing function and then match the parsed protocol property to an allow list.

Use a Sanitation Library

With ReactJS, HTML can be inserted directly into rendered DOM nodes with dangerouslySetInnerHTML, which makes it possible for developers to directly insert HTML content within an HTML element in a React application. When you insert content in such a manner, it must always be sanitized using a sanitization library, such as DOMPurify. Use this on any values before they are placed into the dangerouslySetInnerHTML prop. An example of this, look like so:

import purify from "dompurify";

<div dangerouslySetInnerHTML={{ __html:purify.sanitize(data) }} />

Check for Known Dependency Vulnerabilities

If you’re using third-party components, make sure you’ve thoroughly checked for any vulnerabilities they might contain. Because those components are pre-built by another programmer (or team), you do not want to just trust those components have been tested for problems. If you’re unsure of a component’s security, test it yourself before implementing it.

Avoid JSON Injection Attacks

JSON data is commonly sent with server-side rendered pages in React. When you do this, always escape < characters with any benign value. When you do this, you avoid injection attacks.

Here’s an example to illustrate this practice:

window.__PRELOADED_STATE__ =   ${JSON.stringify(preloadedState).replace( /</g, ’\\u003c’)}

Use Secure React Releases

Always make sure you are using the latest React release. Otherwise, you risk using a version that contains vulnerabilities, some of which could be critical. This holds true for both react and react-dom.

Use a Linter

There are tools, called linters, that can help detect any security issues in your code base. One such linter is ESLint React Security config tool, which is open source and available for free. Linters should not only be used on your custom code but on library code as well. It’s always better to be safe than sorry.

#4 React Authentication Best Practices

Use a Library

When you have to depend on authentication for your app, it’s always best to rely on a library. Remember, authentication is serious business and can lead to a lot of risks. Instead of writing custom code, which can be rife with security issues, turn to a pre-built library for the feature. You’ll find plenty of React authentication libraries available that can serve many different functions. Find one that fits your needs and use it.

Don’t Store Sensitive Data in Local Storage

At some point you’re going to have to store sensitive data, some of which could be user/client data. When you store data in local (client-side) storage, it means anyone who has access to the device (or even third-party applications) can access that data, which could lead to security problems. Instead of storing that 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 Your API Endpoints

One point of security that some overlook is the API endpoints. The thing about endpoints is that if an attacker manages to gain access to one (or more), they will have access to everything, including client data. One method of protecting API endpoints is with JSON Web Tokens (JWTs), which is an industry-standard method of securely transmitting information. Before information is transmitted, the token must be verified on your server.

If JWTs aren’t an option, you should at least be using HTTPS for data encryption. That way, if an attacker were to gain access to an API, at least the data is encrypted and cannot be easily read. Using HTTPS also has the added benefit of protecting against man-in-the-middle attacks, which is yet another way an attacker can gain access to your data.

Encrypt Passwords and Other Sensitive Information

Speaking of encryption, never leave passwords and other sensitive information unencrypted. Doing so leaves your app or service wide open for attack. Even worse, if your app or service is used by consumers or clients, and they store passwords and other information on the app, if that information is unencrypted, that data can be accessed and read by hackers. Leaving client/consumer data wide open like that should never be done.

Implement Rate Limiting on Login Attempts

If you aren’t using rate limiting for login attempts, you leave your app or service open for brute-force attacks. With this in play, it’s only a matter of time before a hacker is able to break into your app/service and steal the data within. You should limit login attempts to, say, five failed attempts within a certain period of time. Should that limit be exceeded, the account should be locked for a set period of time. When doing this, your system will have a chance to detect and report on those failed attempts. Without rate limiting, attackers could keep hammering away at logins until they get it right and gain access to your application or service.

Use 2FA or MFA for Extra Security

Many users balk at having to use either two-factor authentication (2FA) or multifactor authentication (MFA) for logins. After they finally understand why this is used (and that it’s not the inconvenience they assumed it to be), users accept these added layers of security. Instead of making 2FA or MFA optional, consider making it mandatory. Even though 2FA and MFA aren’t perfect, they certainly do make it more challenging for would-be attackers to gain entry to your app or service. Any extra security you can add to your application, service, or website should be considered a must.

Don’t use Email as the Username

Speaking of account security, consider forbidding email addresses as usernames. Why? Email addresses for usernames is a very common practice, which means hackers have less problems guessing one criteria of the login. Instead of using email addresses for usernames, consider forcing users to create unique usernames, which makes it considerably more challenging for a hacker to crack authentication, thereby making your application considerably more secure.

Use Passwordless Authentication

One of the more secure methods of authentication is passwordless. Instead of using a typical password for login, users receive a one-time code that is sent to their phone number, and they then use the code for login. Even more secure is using a code from an authenticator app, such as Authy or Google Authenticator. Because these codes are one-time-use only, there’s no way for a hacker to guess the password — as there is no password to guess. The one downfall of passwordless authentication is when the codes are sent, via SMS, to a phone. Those codes can be intercepted by hackers, which would make it considerably easier to hack into an account. Of course, if the user is working with a unique username (and not an email address), even if the hacker intercepts the code, they’ll have to know the username associated with the account in order to gain access.

Log Out Inactive Users

One thing about mobile and web apps is that users will work with them for a while and either get bored with the app or no longer find a need to use it. In the case of a mobile app, those forgotten apps tend to remain installed and accounts logged in. That could be a security issue. To get around that, consider building into your app/service an automated logout after a set period of time. When you automatically log those users out of the app, a third party wouldn’t be able to unlock their phone and open the app without having to first authenticate. Yes, it might be a slight inconvenience for those users who don’t regularly open the app, but such a convenience is nothing compared to a hacked account. Should one user’s account get hacked, there’s no telling if the offending bad actor couldn’t then gain access to other accounts, an API endpoint, or your hosting server.

 ReactJS Best Practices: Conclusion

ReactJS is widely used across the globe for building interactive interfaces for both web and mobile apps. Given how widespread these types of apps are, it is incumbent on developers to follow best practices at all times to avoid hackers gaining access to sensitive data, which can lead to more trouble than you can imagine.

With just a bit of caution up front, you can avoid such a catastrophe. The end result is that your customers and clients will trust your app and your company. That level of trust cannot be bought or sold, so it should be considered a priceless commodity.

If you enjoyed this article on React, check out these these topics;

BairesDev Editorial Team

By BairesDev Editorial Team

Founded in 2009, BairesDev is the leading nearshore technology solutions company, with 4,000+ professionals in more than 50 countries, representing the top 1% of tech talent. The company's goal is to create lasting value throughout the entire digital transformation journey.

Stay up to dateBusiness, technology, and innovation insights.Written by experts. Delivered weekly.

Related articles

Software Development - Svelte Vs React:
Software Development

By BairesDev Editorial Team

14 min read

react-unit-testing-jest
Software Development

By BairesDev Editorial Team

15 min read

Contact BairesDev
By continuing to use this site, you agree to our cookie policy and privacy policy.