If your development teams work with ReactJS, you should consider these best practices.
If you're researching the best way to structure your React app's folder structure, you've no doubt come across a wide variety of opinions. While this topic can be a bit subjective, there are some guidelines it's best to follow. So whether you're completely new to React development or already experienced with a different opinion, there's something here for everyone.
#1 Best practices for React folder structure
Group folders by resources
If you know the features that will be included in your ReactJS application, you can easily create a folder structure based on these key features. By doing this, you can maintain a well-organized directory structure for your application and you will know exactly which code goes where. This also makes it much easier to debug and collaborate.
Avoid deep nesting
When creating your folder structure, you don't want to end up with lots of nested folders. If you're doing this at first, you might need to rethink your folder hierarchy and break things down more granularly. One of the biggest problems with deep nesting is that it becomes challenging to write relative imports between these deeply nested folders or to update them when files are moved.
Overthinking is your enemy
When starting the process of creating your project's folder structure, don't overthink it. If you're having trouble getting started (because your brain is thinking in circles), just dump all the starter files into the document root. As the project continues, you will see exactly what the folder structure should look like. Once that's clear, create the folders and move the files to their new homes. You can even start with a src folder inside the project's base folder and then move files out of that folder as things become obvious. Just make sure you don't leave your project in this single folder structure as things can 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 your 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
Separate components and utilities
You can also structure your folders by separating components from hooks. This type 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 is a big difference between behavior and implementation. The difference is simple:
- When testing behavior, you don't care how you arrive at the answer, only whether the answer is correct under a given set of circumstances.
- When testing the implementation, you don't care what the answer is, you just do something specific while you figure it out.
Behavior testing is more likely to reach a viable, repeatable conclusion, so testing this way is your best option.
Use custom rendering methods for dependency models
By using a custom rendering method to simulate dependencies, you can avoid having to go through the same configuration every time you run a test. As your project scales, you certainly won't want to go through the same setup for every test. With custom rendering methods, your simulation tests are also more repeatable and easier to maintain.
Test functionality, not implementation
If you focus on testing implementation details, what you're doing is testing how your code is written, not what it does. When going this route, if you change your code, your tests will fail, even if the functionality of the code has not changed. If you instead test functionality, those tests remain viable even if you change the implementation of your code.
Write tests that don't break when the UI changes
Your UI will change. This is inevitable. When creating your tests, you should keep this in mind and write them in such a way that they will not break if the UI changes. Be sure to create tests that focus on the behavior of a component rather than the implementation. For example, instead of testing whether a menu uses the correct CSS class, you should test to make sure clicking the menu provides the expected results.
Don't test what React already tests
The React library is already very well tested. When using library components, you won't need to test them. Instead, focus your testing on the components and code you write. Avoid such redundancies as they only waste time.
Use event simulation helpers
React includes several simulation helpers — such as change , click , and keydown — that simulate events without actually triggering them. Make sure to use these helpers, rather than fireEvent, as this actually dispatches an event to the DOM. Given that the DOM node cannot handle certain types of events, this can cause problems.
Always use Act to test asynchronous events
Along the same lines, you should always use act to test asynchronous events. With asynchronous events, the React Testing Library will wait for tasks to complete before executing any assertions. The problem is that it only works if the asynchronous tasks are started with an event handler. To avoid this problem, wrap your async task in an act call so that the React Testing Library stays until all async tasks complete before running the assertions.
Assert only one thing per test
This is critical. If you assert multiple things per test and one of them fails, the test will fail and you won't know which assertion is causing the problem. Instead, focus your tests on a single assertion so that if the test fails, you know exactly which assertion is problematic.
Keep your tests simple and focused
Another important aspect of testing is keeping it simple. When you create overly complex tests, it becomes more challenging to debug problems. This is especially true as your project becomes more complex.
#3: React Security Best Practices
Use standard cross-site scripting protection with data binding
Although React is quite secure, it can still be vulnerable to things like cross-site scripting (XSS). For example, you should always use keys for standard data binding. This ensures that React will automatically escape values to protect against XSS attacks. Here is an example.
Instead of:
<form action={data}>
Use this:
<div>{date}</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 combine the parsed protocol property with a whitelist.
Use a sanitation library
With ReactJS, HTML can be inserted directly into DOM nodes rendered with dangerousSetInnerHTML, which makes it possible for developers to directly insert HTML content inside an HTML element in a React application. When inserting content this way, it should always be sanitized using a sanitization library such as DOMPurify. Use this on any values before they are placed in the dangerouslySetInnerHTML property. An example of this is like this:
import purify from "dompurify"; <div dangerouslySetInnerHTML= {{ __html:purify.sanitize(data) }} />
Check for known dependency vulnerabilities
If you are using third-party components, carefully check them for vulnerabilities they may contain. Because these components are pre-built by another programmer (or team), you don't want to just rely on these components being tested for issues. If you are unsure of the security of a component, test it before deploying it.
Avoid JSON injection attacks
JSON data is commonly sent with server-side rendered pages in React. When doing this, always escape < characters with any benign value. By doing this, you avoid injection attacks.
Here is an example to illustrate this practice:
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace( /</g, '\\u003c')}
Use secure versions of React
Always make sure you are using the latest version of React. Otherwise, you risk using a version that contains vulnerabilities, some of which may be critical. This applies to both reacting and reacting.
Use a linter
There are tools, called linters, that can help detect any security issues in your codebase. One such linter is ESLint React Security Configuration Tool , which is open source and available for free. Linters should not only be used in your custom code, but also in library code. It's always better to be safe than sorry.
#4 React authentication best practices
Use a library
When you need to rely on authentication for your application, it is always better to rely on a library. Remember that authentication is serious business and can lead to many risks. Instead of writing custom code, which can be fraught with security issues, turn to a pre-built library for the resource. You will find many React authentication libraries available that can serve many different functions. Find one that suits your needs and use it.
Don't store sensitive data on local storage
At some point you will have to store sensitive data, some of which may be user/customer data. When you store data in local (client-side) storage, it means that anyone who has access to the device (or even third-party applications) can access that data, which can lead to security issues. Instead of storing this data in local storage, use a more secure option such as session storage. Better yet, don't store sensitive information on the local device… period.
Secure your API endpoints
One security point that some overlook are API endpoints. The problem with endpoints is that if an attacker manages to gain access to one (or more), they have access to everything, including customer data. One method of securing API endpoints is with JSON Web Tokens (JWTs), which are an industry standard method for securely transmitting information. Before the information is transmitted, the token must be verified on your server.
If JWTs are not an option, you should at least use HTTPS for data encryption. This way, if an attacker gains access to an API, at least the data will be 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 for an attacker to 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 application or service open to attack. Even worse, if your app or service is used by consumers or clients and they store passwords and other information in the app, if that information is not encrypted, that data can be accessed and read by hackers. Leaving customer/consumer data open in this way should never be done.
Implement rate limiting on login attempts
If you are not using rate limiting for login attempts, you leave your application or service open to brute force attacks. With this at stake, it is only a matter of time before a hacker manages to break into your application/service and steal the data contained within it. You should limit login attempts to, say, five failed attempts in a given period of time. If this limit is exceeded, the account must be blocked for a certain period of time. By doing this, your system will have a chance to detect and report these failed attempts. Without rate limiting, attackers can keep hacking logins until they get it right and gain access to your app or service.
Use 2FA or MFA for extra security
Many users are hesitant to use two-factor authentication (2FA) or multi-factor authentication (MFA) for logins. Once they finally understand why this is used (and that it is not the inconvenience they assumed), users accept these additional layers of security. Instead of making 2FA or MFA optional, consider making it mandatory. While 2FA and MFA aren't perfect, they certainly make it more challenging for would-be attackers to gain access to your app or service. Any extra security you can add to your application, service or website should be considered mandatory.
Do not use email as username
Speaking of account security, consider banning email addresses as usernames. Why? Email addresses for usernames are a very common practice, which means hackers have less trouble guessing a login criteria. Instead of using email addresses for usernames, consider forcing users to create unique usernames, which makes it considerably more challenging for a hacker to break authentication, making your application considerably more secure.
Use passwordless authentication
One of the most secure authentication methods is passwordless. Instead of using a typical password to log in, users receive a unique code that is sent to their phone number and then use the code to log in. Even safer is to use a code from an authenticator app, like Authy or Google Authenticator. Since these codes are single-use, there is no way for a hacker to guess the password – as there is no password to guess. The only downside to passwordless authentication is when codes are sent, via SMS, to a phone. These codes can be intercepted by hackers, which would make it considerably easier to break into an account. Of course, if the user is working under a unique username (and not an email address), even if the hacker intercepts the code, he will have to know the username associated with the account to gain access.
Disconnect inactive users
One thing about mobile and web apps is that users will work with them for a while and get bored with the app or no longer find the need to use it. In the case of a mobile app, these forgotten apps tend to remain installed and accounts logged in. This could be a security issue. To get around this, consider incorporating an automatic logout after a certain period of time into your application/service. When you automatically log these users out of the app, third parties cannot unlock the phone and open the app without first authenticating themselves. Yes, it might be a minor inconvenience for users who don't open the app regularly, but such convenience is nothing compared to a hacked account. If a user's account is hacked, there's no way to know that the offending bad actor won't be able to gain access to other accounts, an API endpoint, or your hosting server.
ReactJS Best Practices: Conclusion
ReactJS is widely used across the world to build interactive interfaces for web and mobile applications. Given the pervasiveness of these types of applications, it is up to developers to always follow best practices to prevent hackers from gaining access to sensitive data, which can cause more problems than you might imagine.
With a little caution from the start, you can avoid such a catastrophe. The end result is that your customers will trust your app and your company. This level of trust cannot be bought or sold, so it should be considered a priceless asset.
If you liked this article about React, check out these topics;
- React UI component libraries
- Top 6 React IDEs and Editors
- Why is React so popular?
- What you need to know about react
- Server-side rendering in React
- React WebSockets: Tutorial