React has become the go-to library for building user interfaces, and while it’s easy to get started, writing scalable and maintainable React code requires a commitment to best practices. In this blog post, we’ll explore 10 essential React best practices that will help you write clean, efficient, and robust code for your projects.
Organize your components based on their functionality and responsibilities. Use folders to group related components, and consider using a common folder structure like the “containers” and “components” pattern for a clear separation of concerns.
my-react-app/
|- public/
| |- index.html
| |- ...
|
|- src/
| |- components/
| | |- Header/
| | | |- Header.js
| | | |- Header.css
| | |
| | |- Footer/
| | | |- Footer.js
| | | |- Footer.css
| | |
| |- pages/
| | |- Home/
| | | |- Home.js
| | | |- Home.css
| | |
| | |- About/
| | | |- About.js
| | | |- About.css
| | |
| |- utils/
| | |- api.js
| | |- helperFunctions.js
| | |- ...
|
|- App.js
|- index.js
|- styles.css
|- ...
Prefer functional components over class components whenever possible. Functional components with React Hooks are more concise, easier to read and perform better. There will be no requirement to use the ‘this’ keyword in the functional component.
import React from 'react';
const FunctionalComponent = () => {
const [count, setCount] = React.useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleIncrement}>Increment</button>
</div>
);
};
For state management, embrace React’s built-in useState
and useReducer
hooks for local state. For more complex global state management, consider using a state management library like Redux, Recoil, Zustand, etc.
import React, { useReducer } from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
default:
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
const handleIncrement = () => {
dispatch({ type: 'INCREMENT' });
};
return (
<div>
<p>Count: {state.count}</p>
<button onClick={handleIncrement}>Increment</button>
</div>
);
};
Follow the principle of immutability to avoid unintended side effects and improve performance. Never modify the state or props directly as there will be no re-rendering; instead, create new objects or arrays when updating the state.
When rendering lists of elements, always provide a unique “key” prop to each item. This allows React to efficiently update and re-render only the necessary components, reducing potential bugs and unnecessary re-renders.
import React from 'react';
const TodoList = ({ todos }) => {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
};
Use conditional rendering to display different components or content based on certain conditions. Utilize ternary operators or the &&
operator to keep the JSX concise and readable.
Using ternary operator:
import React from 'react';
const ConditionalRender = ({ isLoggedIn }) => {
return isLoggedIn ? <p>Welcome, user!</p> : <p>Please log in.</p>;
};
Using &&
operator:
import React from 'react';
const ConditionalRender = ({ isLoggedIn }) => {
return isLoggedIn && <p>Welcome, user!</p>;
};
Identify repeating UI patterns and extract them into reusable components. This promotes a DRY (Don’t Repeat Yourself) code base and makes maintenance clean and easier.
Reusable Button component:
import React from 'react';
const Button = ({ text, onClick }) => {
return <button onClick={onClick}>{text}</button>;
};
export default Button;
Usage in other components:
import React from 'react';
import Button from './Button';
const App = () => {
const handleClick = () => {
// Handle button click
};
return (
<div>
<Button text="Click me" onClick={handleClick} />
</div>
);
};
Optimize performance by implementing techniques like code splitting, lazy loading, and memoization. Use tools like React’s built-in React.memo
or useMemo
hooks to prevent unnecessary re-renders.
import React, { useMemo } from 'react';
const ExpensiveComponent = ({ data }) => {
const result = useMemo(() => {
// Expensive calculations based on data
// ...
return 'Result: ...';
}, [data]);
return <div>{result}</div>;
};
Enforce strong typing in your React applications using either Prop Types or Typescript. This catches potential bugs early and improves the overall code quality.
Prop Types:
import React from 'react';
import PropTypes from 'prop-types';
const Person = ({ name, age }) => {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
);
};
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
};
export default Person;
Typescript:
import React from 'react';
interface PersonProps {
name: string;
age: number;
}
const Person: React.FC<PersonProps> = ({ name, age }) => {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
);
};
export default Person;
Implement proper error handling in your components. Use try-catch
blocks in life-cycle methods or Error Boundaries to gracefully handle errors and prevent application crashes.
import React, { useState } from 'react';
const ErrorComponent = () => {
const [errorMessage, setErrorMessage] = useState('');
const handleClick = () => {
try {
// Simulate an error by accessing a non-existent property
const undefinedVar = null;
console.log(undefinedVar.nonExistentProperty);
} catch (error) {
// Handle the error gracefully
setErrorMessage('Something went wrong. Please try again later.');
}
};
return (
<div>
<button onClick={handleClick}>Trigger Error</button>
{errorMessage && <div>{errorMessage}</div>}
</div>
);
};
export default ErrorComponent;
By following these ten React best practices, you’ll be able to build more maintainable, performant, and scalable React applications. Remember to always strive for simplicity, readability, and consistency in your code base. Happy coding!
Source: Medium