25 concrete patterns I keep coming back to when reviewing React code — from render gotchas and state pitfalls to hooks that most teams underuse.
Section 1: Performance Optimization Tips
1. Avoid Declaring Components Inside Other Components
Declaring components inside other components may seem convenient but can lead to unnecessary re-creations and performance issues. Extract nested components to maintain their state and avoid re-renders during parent updates.
// Incorrect
const UsersList = () => {
const User = ({ user }) => <div>{user.name}</div>;
return <User user={{ name: 'John' }} />;
};
// Correct
const User = ({ user }) => <div>{user.name}</div>;
const UsersList = () => <User user={{ name: 'John' }} />;2. Use the Dependency Array Correctly in useEffect
Always include external variables referenced inside useEffect in the dependency array. This prevents bugs like stale closures and ensures the effect runs only when necessary.
useEffect(() => {
fetchUserData(id);
}, [id]); // Include `id` in dependencies3. Lazy-Initialize State with useState
For expensive computations, initialize state lazily using a function. This ensures the computation runs only when the component renders for the first time.
const [value, setValue] = useState(() => calculateInitialValue());4. Memoize Expensive Calculations with useMemo
Use useMemo to cache results of expensive functions and avoid redundant computations during renders.
const sortedUsers = useMemo(() => users.sort((a, b) => a.age - b.age), [users]);5. Create Reusable Logic with Custom Hooks
Extract commonly used logic into custom hooks for better reusability and cleaner components.
function useFetch(url) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData);
}, [url]);
return data;
}6. Use React.Fragment to Avoid Extra DOM Nodes
Group elements without adding unnecessary DOM nodes by using React.Fragment or shorthand <> </>.
return (
<>
<Child1 />
<Child2 />
</>
);7. Use Composite Components for Flexible APIs
Build components that can be composed together for flexible UI construction.
function Tabs({ children }) {
return <div>{children}</div>;
}
Tabs.Tab = ({ label, children }) => (
<div>
<h3>{label}</h3>
<div>{children}</div>
</div>
);
// Usage
<Tabs>
<Tabs.Tab label='Tab 1'>Content 1</Tabs.Tab>
</Tabs>;8. Always Use Unique Keys When Rendering Lists
Use a unique key prop for list items to help React identify changes efficiently. Avoid using array indices as keys.
{
items.map(item => <Item key={item.id} {...item} />);
}9. Implement Lazy Loading with IntersectionObserver
Optimize long pages by loading content, like images or videos, only when they appear in the viewport.
useEffect(() => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadContent();
}
});
});
observer.observe(elementRef.current);
}, []);10. Minimize Unnecessary useEffect and setState Calls
Avoid redundant state updates or effects that trigger unnecessary re-renders.
// Avoid
useEffect(() => {
setValue(computeValue());
}, [dependency]);
// Prefer local variables
const value = computeValue();Section 2: Code Practices and Best Practices
Use Error Boundaries for reliable Apps
Prevent crashes in production by wrapping components with error boundaries.
<ErrorBoundary FallbackComponent={ErrorPage}>
<App />
</ErrorBoundary>12. Break Large Components into Smaller Ones
Split large components into smaller, reusable pieces for better readability and maintainability.
const Header = () => <header>{/* Header code */}</header>;
const Footer = () => <footer>{/* Footer code */}</footer>;13. Always Provide Initial Values for useState
Avoid uninitialized state to prevent runtime issues.
const [items, setItems] = useState([]);14. Simplify Logic with Unified Functions
Combine related functions to reduce duplication.
const toggleModal = () => setIsOpen(open => !open);15. Understand State Update Behavior
React state updates are asynchronous. Use the callback or derived values carefully to avoid stale data.
setCount(prevCount => prevCount + 1);16. Avoid Overusing Context
Use Context for deep component trees. Avoid it for simple prop passing as it introduces unnecessary complexity.
*17. Use null as Initial State for Objects**
Initialize objects in state with null instead of empty objects to avoid unintended behavior.
const [user, setUser] = useState(null);18. Create a Provider Component for Context
Encapsulate Context.Provider in a custom provider component to prevent redundant renders.
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState(null);
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
};19. Avoid Logical Operators for Conditional Rendering
Prevent accidental rendering of unwanted values like 0 by using ternary operators or Boolean casts.
{
users.length > 0 ? <UserList /> : null;
}Section 3: State Management
20. Use Context to Avoid Prop Drilling
use Context to share global data like themes or user settings without deeply nested props.
const ThemeContext = React.createContext();Section 4: Advanced Features
21. Use useRef for Persistent Values
Avoid re-renders for values like timers or DOM references by using useRef.
const interval = useRef(null);22. Implement Code Splitting with React.Suspense
Load components dynamically to reduce initial bundle size.
const Dashboard = lazy(() => import('./Dashboard'));23. Enable React.StrictMode During Development
Identify unsafe lifecycle methods or deprecated APIs with StrictMode.
<React.StrictMode>
<App />
</React.StrictMode>Section 5: General Best Practices
24. Keep State Local When Possible
Localize state management for tightly coupled components to avoid unnecessary complexity.
25. Always Define Default Values for State
Always assign default values to state variables.
const [data, setData] = useState([]);Conclusion
You don’t need all 25 at once. Pick the 3–4 patterns that match your current pain points, apply them in a real PR, and measure the difference. That’s worth more than memorizing the whole list.