This content originally appeared on DEV Community and was authored by Resource Bunk
- It's an giveaway guide Download For FREE
React is constantly evolving—and to stay ahead of the curve, it pays to work smarter rather than harder. In this guide, we cover 15 advanced React techniques that not only boost efficiency but also lead to cleaner, more maintainable code. Whether you’re fighting stale state issues, trying to optimize renders, or managing complex state logic, these hacks have got you covered.
1. Use the useState Updater Function
When updating state, using a callback in setState helps avoid pitfalls like stale state. Instead of doing this:
// Risk: potential stale state if multiple updates happen quickly
setCount(count + 1);
Use the updater form:
setCount(prevCount => prevCount + 1);
This ensures that you always work with the latest state value.
Learn more about useState in the official React docs
2. Optimize Renders with React.memo
Wrapping your functional components with React.memo prevents unnecessary re-renders by memoizing the rendered output unless props change.
const MyComponent = React.memo(({ data }) => {
return <div>{data}</div>;
});
This is especially useful when dealing with expensive component trees or lists.
Explore React.memo in the React docs
3. Use the useEffect Cleanup Function
Always return a cleanup function in your useEffect to manage subscriptions, timers, or event listeners. This helps prevent memory leaks.
useEffect(() => {
const timer = setInterval(() => console.log('Tick'), 1000);
return () => clearInterval(timer); // Cleanup on unmount
}, []);
This practice keeps your components lean and prevents unwanted side effects.
Detailed explanation in React docs
4. Short-Circuit Rendering with && and ||
For cleaner, more concise conditional rendering, use logical operators instead of ternaries when possible.
// Show spinner when loading
{isLoading && <Spinner />}
// Using nullish coalescing for default values:
const displayedValue = value ?? 'Default';
This avoids overly verbose code and makes your JSX more readable.
5. Leverage useCallback and useMemo for Performance
Memoizing functions and values prevents unnecessary re-computations and re-renders. Use useCallback for functions and useMemo for expensive computations:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const handleClick = useCallback(() => {
console.log('Button clicked!');
}, []);
These hooks are essential for optimizing performance in components that deal with heavy computations or deep prop trees.
Read more about these hooks on Medium
6. Use the Nullish Coalescing Operator (??)
Unlike the || operator—which can treat valid falsy values like 0 as defaults—the nullish coalescing operator only falls back on null or undefined.
const displayCount = count ?? 'No count available';
This subtle difference can prevent bugs when working with numeric or boolean values.
7. Set Default Props with Destructuring
Instead of writing conditional expressions within your component, use destructuring with default values to simplify code.
const Greeting = ({ name = 'Guest' }) => {
return <h1>Hello, {name}!</h1>;
};
This technique leads to cleaner and more predictable component behavior.
8. Lazy Load Components with React.lazy and Suspense
Lazy loading reduces the initial bundle size by loading components only when needed. Combine React.lazy with Suspense for an elegant solution.
import React, { Suspense, lazy } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
This method can dramatically improve load times in large applications.
Check out the official guide on code splitting
9. Use the useReducer Hook for Complex State Management
For state that’s too complex for useState, switch to useReducer. This hook is perfect for managing state transitions with a reducer function.
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
};
This approach brings structure and predictability to state management in your components.
10. Utilize Fragments to Avoid Extra DOM Elements
When you need to group elements without adding extra nodes to the DOM, React Fragments are your friend.
return (
<>
<h1>Welcome</h1>
<p>This is a simple example using Fragments.</p>
</>
);
This keeps the DOM clean and avoids unnecessary wrappers.
Learn about Fragments in the React docs
11. Use Conditional Class Names with Libraries
Managing dynamic classes can be messy. Libraries like clsx or classnames simplify this task by conditionally joining class names.
import clsx from 'clsx';
const Button = ({ primary }) => {
return (
<button className={clsx('btn', { 'btn-primary': primary, 'btn-secondary': !primary })}>
Click me
</button>
);
};
These libraries allow you to write expressive and clean className logic.
Explore clsx on npm
12. Handle Errors with Error Boundaries
To prevent your entire UI from crashing due to a single error, wrap critical components in an error boundary.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so next render shows fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log error details to an error reporting service
console.error("Error caught in ErrorBoundary:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Usage:
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
Error boundaries help maintain application stability by catching errors at a granular level.
More details in the React docs
13. Prefetch Data Using React Query
React Query simplifies data fetching by caching and synchronizing server state in your application. It abstracts away many manual data-fetching concerns.
import { useQuery } from 'react-query';
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
};
const DataComponent = () => {
const { data, error, isLoading } = useQuery('dataKey', fetchData);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{JSON.stringify(data)}</div>;
};
This library helps boost performance and provides a great developer experience.
React Query documentation
14. Use useNavigate Instead of useHistory in React Router
With React Router v6, useHistory has been replaced by useNavigate for programmatic navigation. This new hook provides a more intuitive API.
import { useNavigate } from 'react-router-dom';
const MyComponent = () => {
const navigate = useNavigate();
const goToHome = () => {
navigate('/');
};
return <button onClick={goToHome}>Go Home</button>;
};
This change streamlines navigation in modern React applications.
React Router documentation on useNavigate
15. Type-Check Props with PropTypes or TypeScript
Ensuring that your components receive the correct props can prevent many runtime bugs. You can use PropTypes or embrace TypeScript for static type checking.
Using PropTypes:
import PropTypes from 'prop-types';
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;
Greeting.propTypes = {
name: PropTypes.string,
};
Greeting.defaultProps = {
name: 'Guest',
};
Using TypeScript:
type GreetingProps = {
name?: string;
};
const Greeting: React.FC<GreetingProps> = ({ name = 'Guest' }) => <h1>Hello, {name}!</h1>;
Both methods ensure your components are robust and easier to maintain.
Learn more about PropTypes here
Conclusion
Modern React development is as much about leveraging powerful new patterns as it is about writing clean, maintainable code. By adopting these 15 advanced techniques—from using the updater function in useState to embracing lazy loading with React.lazy, and from managing complex state with useReducer to protecting your app with error boundaries—you’re well on your way to building high-performance, scalable applications.
Additional Resources:
- React Official Documentation
- Advanced React Techniques on Medium
- JavaScript in Plain English – Advanced React Patterns
- React Query Documentation
Keep experimenting, keep learning, and most importantly—happy coding!
Feel free to bookmark this guide and refer back whenever you need to supercharge your React projects.
💰 Want to Earn 40% Commission?
Join our affiliate program and start making money by promoting well crafted products! Earn 40% on every sale you refer.
🔗 Sign up as an affiliate here: Become an Affiliate
This content originally appeared on DEV Community and was authored by Resource Bunk
Resource Bunk | Sciencx (2025-02-13T18:50:03+00:00) 😱 You’re Coding React Wrong! Fix It with These 15 Pro Hacks. Retrieved from https://www.scien.cx/2025/02/13/%f0%9f%98%b1-youre-coding-react-wrong-fix-it-with-these-15-pro-hacks/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.