DEV Community

Cover image for Top 25 React Tips Every Developer Should Know — Part 2
Yogesh Chavan
Yogesh Chavan

Posted on • Edited on • Originally published at Medium

Top 25 React Tips Every Developer Should Know — Part 2

This is part two of this React Tips series. If you have not checked the part one, then you can check it out here.

So let’s get started.

1. Use LocatorJS Browser Extension To Quickly Navigate Between Source Code

LocatorJS

Use LocatorJS browser extension to quickly navigate to the source code in your favorite IDE (VS Code) by just clicking on any of the UI elements displayed in the browser.

This extension is really useful when you have a large application and you want to find which file contains the code of the displayed UI.

It’s also useful when the code is written by someone else and you want to debug the code.

Use Option/Alt + Click to navigate to the source code.

This extension becomes a lifesaver when you’re working with Material UI library code written by others and you want to find out the exact code for any part of the UI.

2. Never Hardcode API URL In The Application

Whenever you’re making an API call, never hardcode the API URL, instead, create src/utils/constants.js or a similar file and add the API URL inside it like this:

export const BASE_API_URL = "https://jsonplaceholder.typicode.com";
Enter fullscreen mode Exit fullscreen mode

and wherever you want to make an API call, just use that constant using template literal syntax like this:

// posts.jsx 

import { BASE_API_URL } from '../utils/constants.js';

const {data} = await axios.get(`${BASE_API_URL}/posts`);

// users.jsx 

import { BASE_API_URL } from '../utils/constants.js';

const {data} = await axios.get(`${BASE_API_URL}/users`);
Enter fullscreen mode Exit fullscreen mode

And with this change, If later you want to change the BASE_API_URL value, then you don’t need to change it in multiple files.

You just need to update it in the constants.js file, it will get reflected everywhere.

PS: You can even store the BASE_API_URL value in the .env file instead of storing it in constants.js file so you can easily change it depending on the development or production environment.

3. Use Barrel Exports To Organize React Components

When you’re working on a large React project, you might have multiple folders containing different components.

In such cases, If you’re using different components in a particular file, your file will contain a lot of import statements like this:

import ConfirmModal from './components/ConfirmModal/ConfirmModal'; 
import DatePicker from './components/DatePicker/DatePicker'; 
import Tooltip from './components/Tooltip/Tooltip';
import Button from './components/Button/Button';
import Avatar from './components/Avatar/Avatar';
Enter fullscreen mode Exit fullscreen mode

which does not look good because as the number of components increases, the number of import statements will also increase.

To fix this issue, you can create an index.js file in the parent folder (components) folder and export all the components as a named export from that file like this:

export { default as ConfirmModal } from './ConfirmModal/ConfirmModal'; 
export { default as DatePicker } from './DatePicker/DatePicker'; 
export { default as Tooltip } from './Tooltip/Tooltip';
export { default as Button } from './Button/Button';
export { default as Avatar } from './Avatar/Avatar';
Enter fullscreen mode Exit fullscreen mode

This needs to be done only once. Now, If in any of the files you want to access any component, you can easily import it using named import in a single line like this:

import {ConfirmModal, DatePicker, Tooltip, Button, Avatar} from './components';
Enter fullscreen mode Exit fullscreen mode

which is the same as

import {ConfirmModal, DatePicker, Tooltip, Button, Avatar} from './components/index';
Enter fullscreen mode Exit fullscreen mode

This is standard practice when working on large industry/company projects.

This pattern is known as the barrel pattern which is a file organization pattern that allows us to export all modules in a directory through a single file.

Here’s a CodeSandbox Demo to see it in action.

4. Storing API Key In .env file In React Application Do Not Make It Private

If you’re using a .env file to store private information like API_KEY in your React app, it does not actually make it private.

Your React application runs on the client side/browser so anyone can still see it If that API_KEY is used as a part of the API URL from the network tab.

The better way to make it private is to store it on the backend side.

Note that, even though using the .env file within React doesn’t guarantee absolute privacy, it’s advisable to follow this practice for an added layer of security.

So nobody will be able to find out the API_KEY directly in your application source code.

Also, make sure not to push .env file to GitHub for security reasons. You can achieve that by adding it to the .gitignore file.

5. Passing a Different key To Component Causes Component Re-Creation

<BookForm 
  key={book.id} 
  book={book}
  handleSubmit={handleSubmit}
/>
Enter fullscreen mode Exit fullscreen mode

Whenever we pass a different key to the component, the component will be re-created again and all its data and state will be reset.

This is sometimes useful If you don’t want to retain the previous information / reset the component state, after some action.

For example, on a single page, you’re opening different modals to display different information, then you can pass a unique key for each modal, so every time you open the modal, you will not have previous information retained and you will be able to display the correct respective modal data.

So that’s one of the reasons you need a unique key that will not change during re-render while using the array map method.

Because whenever the key changes, React re-creates that element and all its child elements including components you have nested inside that parent element, causing major performance issues.

6. Organize Components by Creating Separate Folders for Each One

Whenever you’re creating any component, don’t put that component directly inside a components folder.

Instead, create separate folders for individual components inside the components folder like this:

--- components
 --- header
   --- Header.jsx
   --- Header.css
   --- Header.test.js
 --- footer
   --- Footer.jsx
   --- Footer.css
   --- Footer.test.js
 --- sidebar
   --- Sidebar.jsx
   --- Sidebar.css
   --- Sidebar.test.js
Enter fullscreen mode Exit fullscreen mode

As you can see above, we have created a header,footer, and sidebar folders inside the components folder, and inside the header folder, all the files related to the header are kept.

The same is the case with footer and sidebar folders.

7. Don’t Use Redux For Every React Application

Don’t use Redux initially itself while building a React app.

For smaller applications using React Context API or useReducer hook is enough to manage the React application.

Following are some of the use cases when you can choose to select Redux.

To Manage Global State: When you have a global application state like authentication, profile info, or data that needs to be shared across multiple components

To Share Data Between Unrelated Components: When your application has multiple pages using routing, and so you can’t lift state up the parent component

Consistent State Updates: Redux enforces a single source of truth for your application’s state, making it easier to update and maintain your application’s state in a consistent manner.

Scalability: Redux can help your application scale by providing a clear pattern for managing state as your application grows in size and complexity.

You can follow this course to learn Redux + Redux Toolkit from scratch.

8. Use console.count Method To Find Out Number Of Re-renders Of Component

console.count

Sometimes we want to know, how many times a particular line of code is executed.

Maybe we want to know how many times a particular function is getting executed.

In that case, we can use a console.count method by passing a unique string to it as an argument.

For example, If you have a React code and you want to find out how many times the component is getting re-rendered then instead of adding console.log and manually counting how many times it’s printed in the console, you can just add console.count('render') in the component

And you will see the render message along with the count of how many times it’s executed.

9. Avoid Declaring A New State For Storing Everything Inside A Component

Before declaring a new state in a component, always think twice If you really need to store that information in the state.

Because once you declare a state variable, you need to call setState to update that state value, and whenever the state changes, React will re-render the component holding that state, as well as all of its direct as well as indirect child components.

So by just introducing a single state, you add an extra re-render in your application, and If you have some heavy child components doing a lot of filtering, sorting, or data manipulation, then updating the UI with new changes will take more time.

If you just need to track some value that you’re not passing to any child component or not using while rendering then use the useRef hook to store that information.

Because updating the ref value will not cause the component to re-render.

10. Don’t Declare Every Function Inside A Component

When declaring a function inside a component, If that function doesn’t depend on the component’s state or props, then declare it outside the component.

Because on every re-render of the component, all the functions in the functional component are re-created again, so If you have a lot of functions in a component, it might slow down the application.

Take a look at the below code:

const getShortDescription = (description) => {
  return description.slice(0, 20);
}
Enter fullscreen mode Exit fullscreen mode

The above function does not have any dependency on state or prop value.

We’re just passing a value to get shortened text. So there is no need to declare it inside the component.

Also, If you need the same short description functionality in different components, then instead of repeating the code in those components, move the function to another file like utils/functions.js and export it from there and use it in the required components.

This makes your components easy to understand.

11. Upgrade Your React Apps to At Least Version 18

With React version 18, you get better application performance as compared to a version less than 18.

With version 18, React batches together multiple state updates into a single re-render cycle. So If you have multiple set state calls in a function like this:

const handleUpdate = (user) => {
  setCount(count => count + 1);
  setIsOpen(open => !open);
  setUser(user);
}
Enter fullscreen mode Exit fullscreen mode

Then just because there are three state update calls, React will not re-render the component three times, instead, it will be rendered only once.

In React version less than 18, only state updates inside event handler functions are batched together but state updates inside promises, setTimeout function, native event handlers, or any other event handlers are not getting batched.

If you’re using a React version less than 18, then for the above code, as there are three state updates, the component will be rendered three times which is performance-in-efficient.

With version 18, state updates inside of timeouts, promises, native event handlers or any other event will batch together so for the above code rendering happens only once.

12. Use Correct Place To Initialize QueryClient In Tanstack Query/React Query

When using Tanstack Query / React Query, never create QueryClient instance inside the component.

The QueryClient holds the Query Cache, so if you create a new client inside a component, using the below code:

const queryClient = new QueryClient();
Enter fullscreen mode Exit fullscreen mode

then on every re-render of the component, you will get a new copy of the query cache. so your query data will not be cached and caching functionality will not work as expected.

So always create QueryClient instance outside the component instead of inside the component.

// Instead of writing code like this:

const App = () => {
 // ❌ Dont' create queryClient inside component. This is wrong.
 const queryClient = new QueryClient();

 return (
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
 );
}

// write it like this:

// ✅ This is correct place to create queryClient
const queryClient = new QueryClient();

const App = () => {
 return (
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
 );
}
Enter fullscreen mode Exit fullscreen mode

Check out this course, If you want to learn React Query / Tanstack Query From Scratch.

13. Use ReacTree VSCode Extension To Find Out Parent Child Relationships

Use ReacTree VS Code extension to quickly see a tree view of your React application components.

It’s a great way to find out the parent and child components relationships.

ReacTree Extension

14. Always Use Libraries Like react-hook-form When Working With Forms

Whenever working with simple as well as complex forms in React, I always prefer the react-hook-form library which is the most popular React form-related library.

It uses refs instead of state for managing input data which helps to avoid performance issues in the application.

With this library, your component will not re-render on every character typed which is the case when you manage it yourself with the state.

You also get the added benefit of an easy way of implementing reactive validations for your forms.

It also makes your component code a lot simpler and easier to understand.

It’s a very powerful library, so even shadcn/ui library uses it for its Form component.

Check out this article, If you want to learn react-hook-form from scratch.

15. Avoid Passing setState Function As A Prop To Child Components

Never pass the setState function directly as a prop to any of the child components like this:

const Parent = () => {
 const [state, setState] = useState({
   name: '',
   age: ''
 })

 .
 .
 .

 return (
  <Child setState={setState} />
 )
}
Enter fullscreen mode Exit fullscreen mode

The state of a component should only be changed by that component itself.

Here’s Why:

  • This ensures your code is predictable. If you pass setState directly to multiple components, it will be difficult to identify from where the state is getting changed.

  • This lack of predictability can lead to unexpected behavior and make debugging code difficult.

  • Over time, as your application grows, you may need to refactor or change how the state is managed in the parent component.

  • If child components rely on direct access to setState, these changes can ripple through the codebase and require updates in multiple places, increasing the risk of introducing bugs.

  • If sensitive data is part of the state, directly passing setState could potentially expose that data to child components, increasing security risks.

  • React’s component reconciliation algorithm works more efficiently when state and props updates are clearly defined within components.

Instead of passing setState directly, you can do the following:

Pass data as props: Pass the data that the child component needs as props, not the setState function itself. This way, you provide a clear interface for the child component to receive data without exposing the implementation details of the state.

Pass function as prop: If the child component needs to interact with the parent component’s state, you can pass a function as a prop. Declare a function in the parent component and update the state in that function, you can pass this function as a prop to the child component and call it from the child component when needed.

16. Avoid Using Nested Ternary Operators

When using the ternary operator never go beyond one condition and avoid writing nested conditions.

// Good Practice
{isAdmin ? <Dashboard /> : <Profile /> }

// Bad Practice
{isAdmin ? <Dashboard /> : isSuperAdmin ? <Settings /> : <Profile />}
Enter fullscreen mode Exit fullscreen mode

Using nested conditions when using a ternary operator makes the code difficult to read and maintain even If you’re using some formatter to format the code.

If you have more than one condition to check, you can use if/else or switch case or lookup map but never use a ternary operator like this:

if (isAdmin) {
 return <Dashboard />;
} else if (isSuperAdmin) { 
 return <Settings />;
} else {
 return <Profile />;
}

// OR

switch (true) { 
 case isAdmin:
   return <Dashboard />; 
 case isSuperAdmin:
   return <Settings />; 
 default:
   return <Profile />; 
}

// OR

{isAdmin && <Dashboard />}
{isSuperAdmin && <Settings />}
{!isAdmin && !isSuperAdmin && <Profile />}

// OR use a lookup map like this:

const data = {
 isAdmin: <Dashboard />,
 isSuperAdmin: Settings />,
 Neither: <Profile />
}

// and access the data using data['isAdmin'], 
// resulting in the Dashboard page being displayed.
Enter fullscreen mode Exit fullscreen mode

This applies not only to React but also to JavaScript and other programming languages.

17. Don’t Create Separate File For Every Component

You don’t have to create every component in a separate file.

If you have a component that is getting used only in a particular file and that component is just returning minimal JSX code, then you can create that component inside the same file.

This avoids unnecessary creation of extra files and also helps to understand code better when it’s in a single file.

const ProfileDetailsHeader = () => {
  return (
    <header>
      <h1 className='text-3xl font-bold leading-10'>
        Profile Details
      </h1>
      <p className='mt-2 text-base leading-6 text-neutral-500'>
        Add your details to create a personal touch to your profile.
      </p>
    </header>
  );
};

const Profile = () => {
  return (
   <ProfileDetailsHeader />
   ...
   <ProfileDetailsFooter />
  );
}
Enter fullscreen mode Exit fullscreen mode

18. Never Store Private Information Directly In The Code

I have seen many people directly writing their private information like Firebase configuration in the code like this:

const config = {
  apiKey: 'AIdfSyCrjkjsdscbbW-pfOwebgYCyGvu_2kyFkNu_-jyg', 
  projectId: 'seventh-capsule-78932',
  ...
};
Enter fullscreen mode Exit fullscreen mode

Never ever do this. It’s a major security issue. When you push the file with this configuration to GitHub, when someone clones your repository, they’re able to directly access your Firebase data.

They can add, edit, or delete the data from your Firebase.

To avoid this issue, you can create a file with the name .env in your project and for each property of the config object, create an environment variable like this:

// If using Vite.js

VITE_APP_API_KEY=AIdfSyCrjkjsdscbbW-pfOwebgYCyGvu_2kyFkNu_-jyg

// access it as import.meta.env.VITE_APP_API_KEY

// If using create-react-app

REACT_APP_API_KEY=AIdfSyCrjkjsdscbbW-pfOwebgYCyGvu_2kyFkNu_-jyg

// and access it as process.env.REACT_APP_API_KEY
Enter fullscreen mode Exit fullscreen mode

Also, add the .env file entry to .gitignore file so it will not be pushed to GitHub.

19. Always Move Your Component Business Logic Into Custom Hooks

Whenever possible, always try to make maximum use of custom hooks.

Always put all your component’s business logic, and API calls inside a custom hook.

This way your component code looks clean and easy to understand, maintain, and test.

const ResetPassword = () => {
   const { isPending, sendResetEmail, error } = useResetPassword();

   // some JSX
}
Enter fullscreen mode Exit fullscreen mode

20. Every Use Of Custom Hook Creates a New Instance

Most beginner React developers make this mistake while understanding the custom hooks.

When using a particular custom hook in multiple components, you might think that each component will refer to the same data returned from that hook like this:

const [show, toggle] = useToggle();
Enter fullscreen mode Exit fullscreen mode

However, this is not the case.

Each component using that hook will have a separate instance of the hook.

As a result, all the states, event handlers, and other data declared in that custom hook will be different for each component using that hook.

So If you need to use the same data and event handlers for all the components, you need to import the custom hook at only one place in the parent component of all of these components.

Then, you can pass the data returned by the custom hook as a prop or use Context API to access it from specific components.

So never make the mistake of using the same custom hook in different components assuming changing hook data from one component will be automatically updated in another component also.

Here’s a CodeSandbox Demo to see it in action.

21. Avoid Losing State Properties When Updating Objects with React Hooks

When using class components, If you have a state with multiple properties like this:

state = {
  name: '',
  isEmployed: false
};
Enter fullscreen mode Exit fullscreen mode

Then to update the state we can specify only the property that we want to update like this:

this.setState({
  name: 'David'
});

// OR

this.setState({
  isEmployed: true
});
Enter fullscreen mode Exit fullscreen mode

So the other state properties will not be lost when updating any property.

But when working with React hooks, If the state stores an object like this:

const [state, setState] = useState({
  name: '',
  isEmployed: false
});
Enter fullscreen mode Exit fullscreen mode

Then you manually need to spread out the previous properties when updating a particular state property like this:

setState((prevState) => {
  return {
    ...prevState,
    name: 'David'
  };
});

// OR

setState((prevState) => {
  return {
    ...prevState,
    isEmployed: true
  };
});
Enter fullscreen mode Exit fullscreen mode

If you don’t spread out the previous state values, the other properties will be lost.

Because the state is not automatically merged when using the useState hook with object.

22. Dynamically Adding Tailwind Classes In React Does Not Work

If you’re using Tailwind CSS for styling and you want to dynamically add any class then the following code will not work.

<div className={`bg-${isActive ? 'red-200' : 'orange-200'}`}>
  Some content
</div>
Enter fullscreen mode Exit fullscreen mode

This is because in your final CSS file, Tailwind CSS includes only the classes present during its initial scan of your file.

So the above code will dynamically add the bg-red-200 or bg-orange-200 class to the div but its CSS will not be added so you will not see CSS applied to that div.

So to fix this, you need to define the entire class initially itself like this:

<div className={`${isActive ? 'bg-red-200' : 'bg-orange-200'}`}>
  Some content
</div>
Enter fullscreen mode Exit fullscreen mode

If you have a lot of classes that need to be conditionally added, then you can define an object with the complete class names like this:

const colors = {
  purple: 'bg-purple-300',
  red: 'bg-red-300',
  orange: 'bg-orange-300',
  violet: 'bg-violet-300'
};
Enter fullscreen mode Exit fullscreen mode

and use it like this:

<div className={colors['red']}>
  Some content
</div>
Enter fullscreen mode Exit fullscreen mode

23. Always Add Default Or Not Found Page Route When Using React Router

Whenever you’re implementing routing in React using the React router, don’t forget to include a Route for an invalid path(not found page).

Because if someone navigates to any route that does not exist, then he will see a blank page.

To set up an invalid Route, you can mention the invalid Route component as the last Route in the list of Routes as shown below.

<Routes>
  <Route path="/" element={<Home />} />
  .
  .
  .
  <Route path="*" element={<NotFoundPage />} />
</Routes>
Enter fullscreen mode Exit fullscreen mode

If you don’t want to display the NotFoundPage component for an invalid route, you can set up redirection to automatically redirect to the home page for the invalid route as shown below.

<Routes>
  <Route path="/" element={<Home />} />
  .
  .
  .
  <Route path="*" element={<Navigate to="/" />} />
</Routes>
Enter fullscreen mode Exit fullscreen mode

24. Use Spread Operator To Easily Pass Object Properties To Child Component

If you have a lot of props that need to be forwarded to a component, then instead of passing individual props like this:

const Users = ({users}) => {
  return (
    <div>
      {users.map(({ id, name, age, salary, isMarried }) => {
        return (
          <User 
            key={id}
            name={name}
            age={age}
            salary={salary}
            isMarried={isMarried}
          />
        )
      })}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

You can use the spread operator to easily pass props like this:

const Users = ({users}) => {
  return (
    <div>
      {users.map((user) => {
        return (
          <User key={user.id} {...user} />
        )
      })}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

However, don’t overuse this spread syntax. If the object you’re spreading has a lot of properties then passing them manually as shown in the first code is better than spreading out.

25. Moving State Into Custom Hook Does Not Stop The Component From Re-rendering

Whenever you’re using any custom hook in any of the components and if the state inside that custom hook changes, then the component using that custom hook gets re-rendered.

Take a look at the below code:

export const useFetch = () => {
 const [data, setData] = useState([]);
 const [isLoading, setIsLoading] = useState(false);

 // some code to fetch and update data

 return { data, isLoading };
};

const App = () => {
 const { data, isLoading } = useFetch();

 // return some JSX
};
Enter fullscreen mode Exit fullscreen mode

As can be seen in the above code, useFetch is a custom hook so whenever the data or isLoading state changes, the App component will get re-rended.

So If the App component is rendering some other child components, those direct child, as well as indirect child components, will also get re-rendered when the data or isLoading state from the useFetch custom hook changes.

So just because you have refactored the App component to move state inside the custom hook does not mean that the App component will not get re-rendered.

Thanks for Reading!

Want to stay up to date with regular content regarding JavaScript, React, and Node.js? Follow me on LinkedIn.

Black Friday Sale — 85% Off - Get Lifetime/Pro Subscription

Master JavaScript, React and Node.js

Subscribe to my YouTube Channel

My GitHub

Top comments (10)

Collapse
 
mateusguedess profile image
Mateus Guedes da Conceição

Nice one!!!

Collapse
 
myogeshchavan97 profile image
Yogesh Chavan

Glad you found it useful. Thank you🙏

Collapse
 
obere4u profile image
Nwosa Tochukwu

Nice. Thank you

I agree that the API should be in the .env file so that it is easier to switch modes (dev or prod).

Collapse
 
myogeshchavan97 profile image
Yogesh Chavan

Glad you found it useful. Thank you🙏

Collapse
 
a_m_h_gad profile image
Jad

Thanks for sharing that ♥️

Collapse
 
myogeshchavan97 profile image
Yogesh Chavan

Glad you found it useful. Thank you🙏

Collapse
 
rohitkhokhar profile image
Rohit Khokhar

I really enjoyed this blog post! Your writing style is clear and captivating, and the way you presented the information was both helpful and inspiring. Keep up the great work!

Collapse
 
myogeshchavan97 profile image
Yogesh Chavan

Glad to hear that. Thank you so much🙏

Collapse
 
asmyshlyaev177 profile image
Alex • Edited

Would disagree about #3, I prefer named exports, and such approach can lead to circular imports. That can dramatically slow down Next.js dev server.

Other advises are useful.

Collapse
 
myogeshchavan97 profile image
Yogesh Chavan

Thanks for adding your views🙏