DEV Community

Cover image for 5 Essential Practices for Front-End Developers (React Edition)
Sufian mustafa
Sufian mustafa

Posted on

5 Essential Practices for Front-End Developers (React Edition)

Introduction

5 Essential Practices for Front-End Developers (React Edition)

Embarking on a new professional journey is often accompanied by excitement and high expectations. However, the reality can be starkly different when faced with a codebase resembling a chaotic puzzle. To mitigate this common scenario, especially for developers in senior roles, adopting specific best practices becomes imperative. This ensures not only code quality but also positions you as a meticulous professional, garnering recognition and potential promotions within the company.

5 Essential Practices for Front-End Developers (React Edition)

1. Optimal Path Handling: Absolute Paths Over Relative Paths

Imagine navigating a maze with nothing but cryptic clues like "go back four steps and turn left twice." That's what relative paths in your code can feel like. Instead, embrace the power of absolute paths! These provide the full address of a file, making imports crystal clear and saving you from endless guessing games. Setting them up might involve some configuration magic with tools like Webpack or TypeScript, but trust us, it's worth the effort.

Bonus Tip: For projects using create-react-app, a simple jsconfig.json file can be your hero. With a few lines of code, you can define a base URL for imports, transforming that monster path ../../../../../components/Button into a sleek @/components/Button.

If you’re using TypeScript, add the following configurations to your “tsconfig.json” file:

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src"]
}
Enter fullscreen mode Exit fullscreen mode

By doing so, you can transform a code snippet that looks like this:

//from this
import { Button } from '../../../../components/Button'
import { Icon } from '../../../../components/Icon'
import { Input } from '../../../../components/Input'
Into something cleaner and easier to read, like:
//to this
import { Button } from '@/components/Button'
import { Icon } from '@/components/Icon'
import { Input } from '@/components/Input'
Enter fullscreen mode Exit fullscreen mode

2. Streamlining Module Organization: The Power of "Export Barrel"

Code readability and maintenance receive a substantial boost with the "export barrel" technique, also known as "re-export." Creating an "index.js" (or "index.ts" for TypeScript) file within a folder and exporting all modules simplifies imports and enhances code organization.
Implementation Example:
Imagine a "components" folder with "Button.tsx," "Icon.tsx," and "Input.tsx." Utilizing the "export barrel," you can create an "index.ts" file to streamline imports:

export * from './Button'
export * from './Icon'
export * from './Input'
Enter fullscreen mode Exit fullscreen mode

This practice not only reduces the need for individual imports but also contributes to a cleaner and more comprehensible codebase—essential for medium to large-scale projects.

3. Choosing Between “Export Default” and “Named Export”

As we delve into the topic of the “export barrel,” it’s essential to note that it can conflict with the use of “export default.” If this is not clear, I’ll illustrate the situation with examples:

Let’s go back to our components:

export const Button = () => {
  return <button>Button</button>
}
export default Button
export const Icon = () => {
  return <svg>Icon</svg>
}
export default Icon
export const Input = () => {
  return <input />
}
export default Input
Enter fullscreen mode Exit fullscreen mode

Imagine each of these components is in a separate file, and you want to import all of them at once. If you are accustomed to default imports, you might attempt something like:

import Button from '@/components'
import Icon from '@/components'
import Input from '@/components'
Enter fullscreen mode Exit fullscreen mode

However, this won’t work because JavaScript can’t determine which “export default” to use, resulting in errors. You would be forced to do something like this:

import Button from '@/components/Button'
import Icon from '@/components/Icon'
import Input from '@/components/Input'
Enter fullscreen mode Exit fullscreen mode

This, however, negates the advantage of the “export barrel.” How can you resolve this dilemma? The solution is simple: use “Named Export,” which is exporting without “default”:

import { Button, Icon, Input } from '@/components'
Enter fullscreen mode Exit fullscreen mode

Another critical issue associated with “export default” is the ability to rename what you’re importing. I’ll share a real-life example from early in my career. I inherited a React Native project where the previous developer used “export default” for absolutely everything. There were screens named “Login,” “Register,” and “ForgotPassword.” However, all three screens were copies of each other with minor modifications. The problem was that, at the end of each screen file, there was an “export default Login.” This resulted in confusion, as the route file imported correctly:

import Login from '../../screens/Login'
import Register from '../../screens/Register'
import ForgotPassword from '../../screens/ForgotPassword'
Enter fullscreen mode Exit fullscreen mode
// Further down, the usage in routes:

  {
    ResetPassword: { screen: ResetPassword },
    Login: { screen: LoginScreen },
    Register: { screen: RegisterScreen },
  }
Enter fullscreen mode Exit fullscreen mode

But when opening the screen files, they all exported the same name:


const login() {
  return <>tela de login</>
}
export default Login
const login() {
  return <>tela de registro</>
}
export default Login
const login() {
  return <>tela de esqueci minha senha</>
}
export default Login
Enter fullscreen mode Exit fullscreen mode

This created a maintenance nightmare with constant confusion and the need for extreme vigilance to avoid errors.

In summary, it’s highly recommended to use “Named Export” in most cases in your project and resort to “export default” only when strictly necessary. There are situations, such as Next.js routes and React.lazy, that may require the use of “export default.” However, it’s crucial to strike a balance between code clarity and compliance with specific requirements.

4. Proper File Naming Conventions

Let’s imagine you have a components folder with the following files:

-

-components:
----Button.tsx
----Icon.tsx
----Input.tsx
Enter fullscreen mode Exit fullscreen mode

Now, suppose you want to separate styles, logic, or types of these components into separate files. How would you name these files? An obvious approach might be the following:

--components:
----Button.tsx
----Button.styles.css
----Icon.tsx
----Icon.styles.css
----Input.tsx
----Input.styles.css
Enter fullscreen mode Exit fullscreen mode

Certainly, this approach can seem somewhat disorganized and challenging to understand, especially when you intend to further divide the component into distinct files, such as logic or types. But how can you keep the structure organized? Here’s the solution:

--components:
----Button
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.tsx
----Icon
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.tsx
----Input
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.tsx
Enter fullscreen mode Exit fullscreen mode

This approach makes it easy to identify the purpose of each file and simplifies the search for what you need. Additionally, if you’re using Next.js or a similar framework, you can adapt the file naming to indicate whether the component is intended for the client or server side. For example:

--components:
----RandomComponent
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.tsx
----RandomComponent2
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.server.tsx
Enter fullscreen mode Exit fullscreen mode

This way, it becomes extremely simple to distinguish whether a component is meant for the client or server side without the need to open the code for verification. Organizing and standardizing file naming is essential to maintain clarity and efficiency in development projects.

5. Proper Use of ESLint and Prettier for Code Standardization

Imagine yourself working on a project with over 10 colleagues, each bringing their own coding style from past experiences. Here’s where ESLint and Prettier come into play. They play a crucial role in maintaining code consistency throughout the team.

Prettier acts as a kind of “guardian” of code formatting, ensuring that everyone adheres to the style guidelines set for the project. If the project’s standard dictates the use of double quotes, for example, you can’t simply opt for single quotes because Prettier will automatically replace them. Furthermore, Prettier can perform various other fixes and formatting, like code alignment, adding semicolons at the end of statements, and more. You can check the specific Prettier rules in the official documentation: Prettier Options.

ESLint, on the other hand, enforces specific rules on the code, helping to maintain a cohesive and coherent codebase. For instance, it can enforce the use of arrow functions over regular functions, ensure that the React dependencies array is properly filled, prohibit the use of “var” declarations in favor of “let” and “const,” and apply naming conventions like camelCase. You can find the specific ESLint rules in the official documentation: ESLint Rules.

The combined use of ESLint and Prettier helps to maintain consistency in source code. Without them, each developer can follow their own style, which can lead to conflicts and maintenance difficulties in the future. Having these tools set up is essential for the longevity of a project, as it helps keep the code organized and easy to understand. If you’re not already using ESLint and Prettier, seriously consider incorporating them into your workflow, as they will greatly benefit your team and your project as a whole.

Conclusion:

By incorporating these best practices into your React development workflow, you contribute to a more organized, readable, and maintainable codebase. Stay committed to improving coding standards, and don't forget to Like 🦄 if you found these practices useful! Happy coding! 🚀

Top comments (27)

Collapse
 
davidrenne profile image
davidrenne • Edited

2: "essential for medium to large-scale projects". Yes if you are using * to import a core library you are asking for trouble in your bundler when used with open source libraries on large projects. Your app might only need certain parts of a library, but asking for * can pull in TONS of code you arent even using. This is bad advice.

Collapse
 
sufian profile image
Sufian mustafa

Thank you so much for your thoughts! Your concern about using * to import an entire library is especially valid in large projects. ð︸ You are right; It may cause unnecessary bloat and affect the overall performance of the application.
The importance of the "export package" in my articles is more related to ease of import in project components and aims to improve organization and readability. However, I completely agree that caution should be exercised, especially when dealing with open source libraries.
For large projects, selecting only appropriate elements from the library is a best practice for optimization and keeping content lean and efficient. ðYour comments add value and I welcome the opportunity to delve deeper into this area.

Collapse
 
snakepy profile image
Fabio

But it is not about the import it is about the export. This shouldn't be an issue?

Collapse
 
davidrenne profile image
davidrenne

Ahh I see I read it wrong. OK gotcha this is about easily creating a huge index for people to lazily get everything for your libs. But it still begs the question that it's not a good practice when large libraries can dump tons of code to importers who may only want a subset of code. Tree shaking doesn't work when you go to an index file and splat the whole thing.

Typically most libraries put a ton of components in their base export index for ease of importing their exports, possibly using the strategy outlined in #2. From an import perspective I find it best to find the folders where each component is located vs using an index. I've seen cases where one person imports an icon using * and imports a Meg or two of svg icons. I update it to just the icon we need and trim the fat and reduce the bundle size.

Most examples of using libraries use this lazy mode of * at the index of the library to get things done quick with example hello world usages of the libs. When sometimes if the package has a lot of components and moving parts it's best to find the folders for each thing you need and target that one vs importing everything in an index file.

I hate index files after they have screwed my bundle size so many times and I spend hours removing index references to get the specific components the callers of the library needed.

Thread Thread
 
darshan260802 profile image
darshan260802

Agree and we can use vscode extensions such as "Import cost" to check how much code is getting imported while using this kind of exports.
I think using named exports to just import necessary part/functions from files exported like this wont be an issue.

@sufian Nice article bro , learned a lot!

Collapse
 
wraith profile image
Jake Lundberg • Edited

Nice article 😊 I definitely agree with some of your points, especially around good naming and directory structure, and configuring aliases for your imports (ie. @components/SomeComponent 😍). However, I do have to disagree with a couple of your suggestions.

  1. You should never use an absolute path for your imports. The reason being that the absolute path on one machine is not going to be the same on another one. This may work in a containerized application, but is definitely not a one-size fits all. If relative paths are a problem for your application, I would Highly recommend taking the time to set up aliases as you mentioned in the first suggestion instead of using absolute paths. 🔃

  2. Barrel files cause lots of problems. Build performance issues, bundle size impacts, breaking of Intellisense and autocompletion, and all the hassle they can cause with Typescript (if you're using it). I can't tell you how many hours I've spent trying to fix circular dependencies that were caused by barrel files! Barrels are for aging wine and whiskey, please don't use them for code! 🥃🍷

To end on a positive note, I 100% agree with #5! Properly formatted and standardized code matters so much. It's so hard to work on a project that lacks the use of tools like these. So great point here!

Thanks for the article and starting some great conversation on the topic of best practices!

Collapse
 
darshan260802 profile image
darshan260802 • Edited

I think for absolute paths he ment @ rule paths provided by TS which will be resolved while compiling by TS and for a project paths starting from SRC will always going to be same in all machines for same project

when he says absolute path
He means this -> "src/component/home.ts"
He doesn't mean this -> "D:/work/react-projects/my-test-project/src/component/home.ts"

Collapse
 
wraith profile image
Jake Lundberg

I can certainly see what the author meant to convey here as you describe. However, the term "absolute path" has a very specific meaning:

Absolute Path is the hierarchical path that locates a file or folder in a file system starting from the root.

What you and the author are referring to actually has different names depending on the tool you're using.

In Next.js, these are called Absolute Imports.
In Svelte, these are called Aliases.
They are also called Aliases in Nuxt.js

I hope this adds some clarity to my origin comment 😃

Collapse
 
meenakshi052003 profile image
Meenakshi Agarwal

Great article! It provides valuable insights for React developers, especially beginners, covering essential practices for writing clean, maintainable, and collaborative code. Just wondering if it could have gone a bit deeper into potential challenges or alternative strategies related to the "export barrel" technique.

Collapse
 
sufian profile image
Sufian mustafa

Thank you so much for the positive feedback! I'm thrilled to hear that you found the article valuable, especially for React developers, and I appreciate your suggestion for diving deeper into potential challenges and alternative strategies related to the "export barrel" technique. 🚀

Collapse
 
morganney profile image
Morgan Ney • Edited

I disagree with #2 and #3. After having used barrel files and default exports regularly, I’ve decided they are mostly anti-patterns. I would prefer explicit imports of module paths and named exports given the option.

After taking a closer look at #3 I see you are advocating for named exports over defaults 👍.

Collapse
 
kurealnum profile image
Oscar

Just a heads up that you can add highlighting to the code blocks if you'd like. Just change:

code block with no colors example

... to specify the language:

code block with colors example

More details in our editor guide! Awesome post!

Collapse
 
anmolbaranwal profile image
Anmol Baranwal

Yep, I was going to suggest the same.

Collapse
 
hyungjunk profile image
hyungjunk • Edited

Does everybody prefer barrel? In my case, sometimes barrel makes me confused where the component is being imported. And I ended up importing the whole other components when I needed only one component. This slows down the test.

Collapse
 
sufian profile image
Sufian mustafa

I totally get your point, and you're not alone in feeling that way about "export barrel" techniques. It's true that personal preferences and project requirements can play a significant role in choosing the right approach.

The potential confusion you mentioned about where a component is being imported is a valid concern. It's crucial to strike a balance between the convenience of streamlined imports and the potential downsides, like accidentally importing more than needed and impacting test performance.

Collapse
 
lundjrl profile image
James Robert Lund III

I could be wrong but doesn't #2 inflate the bundle size? I think each exported file is included in your chunk when building the app. This happened to us with Gatsby SSG

Collapse
 
clschnei profile image
Chris Schneider • Edited

Seeing others comment about #2, and here is a thorough explanation on why this is something to avoid for the time being:

marvinh.dev/blog/speeding-up-javas...

Collapse
 
wesborland profile image
Marcos Javier Gomez Hollger

Thanks for sharing your tips to improve in React! ❤️

Collapse
 
sufian profile image
Sufian mustafa

You're very welcome! ❤️ I'm glad you found the tips helpful

Collapse
 
ricardogesteves profile image
Ricardo Esteves

Nice article, thanks for sharing it!

Collapse
 
sufian profile image
Sufian mustafa

You're welcome! 😊 I'm thrilled that you enjoyed the article.