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
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";
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`);
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';
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';
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';
which is the same as
import {ConfirmModal, DatePicker, Tooltip, Button, Avatar} from './components/index';
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}
/>
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
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
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);
}
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);
}
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();
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>
);
}
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.
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} />
)
}
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 />}
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.
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 />
);
}
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',
...
};
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
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
}
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();
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
};
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
});
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
});
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
};
});
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>
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>
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'
};
and use it like this:
<div className={colors['red']}>
Some content
</div>
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>
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>
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>
)
}
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>
)
}
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
};
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.
Master JavaScript, React and Node.js
Top comments (2)
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.
Thanks for adding your views🙏