A Deep Dive into React Hooks. Best Practices, Common Pitfalls.

Roman Sypchenko
4 min readApr 17, 2023

--

Photo by Fili Santillán on Unsplash

Introduction

In the world of React, hooks have taken center stage, and for a good reason! React Hooks simplify component logic, making it easier to share and reuse stateful logic across components. In this article, we’ll dive deep into React Hooks, explore some best practices, common pitfalls, and sprinkle in a few jokes and GIFs to keep you hooked. Let’s get started!

  1. useState: Keeping State Simple

The useState hook allows you to add state to functional components easily. It returns a pair of values: the current state and a function to update it.

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times.</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}

Best Practice: Initialize state with a function when the initial state is computationally expensive.

const [complexState, setComplexState] = useState(() => computeInitialState());

Common Pitfall: Don’t forget that updating state with the same value doesn’t trigger a re-render. If you’re not careful, you might be left wondering why your component isn’t updating.

2. useEffect: Side Effects Made Easy

The useEffect hook lets you perform side effects in functional components, such as data fetching, subscriptions, or manual DOM manipulations.

import React, { useState, useEffect } from 'react';

function Example() {
const [data, setData] = useState([]);

useEffect(() => {
fetchData().then((fetchedData) => setData(fetchedData));
}, []);

// Render data...
}

Best Practice: Don’t forget to add the correct dependencies in the dependency array. If you don’t, you might end up with stale data or infinite loops.

useEffect(() => {
// Your effect...
}, [dependency1, dependency2]);

Common Pitfall: Keep in mind that effects run after every render, so make sure your effect code is idempotent.

3. useCallback: Memoizing Functions

The useCallback hook allows you to memoize a function so that it doesn’t change on every render unless its dependencies change.

import React, { useState, useCallback } from 'react';

function App() {
const [count, setCount] = useState(0);

const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);

return (
<>
<p>Count: {count}</p>
<Button onClick={increment}>Increment</Button>
</>
);
}

Best Practice: Use useCallback only when necessary, such as when passing a function as a prop to a child component that uses React.memo() or PureComponent.

Common Pitfall: Don’t overuse useCallback. In some cases, it might cause more harm than good by consuming more memory and causing unnecessary re-renders.

4. useContext: Sharing Context Like a Pro

import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemedButton() {
const theme = useContext(ThemeContext);

return <button style={{ background: theme.background, color: theme.foreground }}>Themed button</button>;
}

Best Practice: useContext should be used with a single context per component, as it helps in improving readability and maintainability.

Common Pitfall: Be cautious when using useContext with multiple contexts in a single component, as it can make your code harder to follow and understand.

5. useReducer: Taming Complex State Logic

The useReducer hook allows you to manage complex state logic in your components using a reducer function, similar to Redux.

import React, { useReducer } from 'react';

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();
}
}

function 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>
</>
);
}

Best Practice: Use useReducer when your component has complex state logic that involves multiple sub-values or when the next state depends on the previous state.

Common Pitfall: Don’t use useReducer for simple state management tasks where useState would suffice. Overusing useReducer can make your code more challenging to understand.

Conclusion

React Hooks have revolutionized the way we write components in React, simplifying state management and side effects in functional components. By keeping these best practices and common pitfalls in mind, you can write clean, efficient, and maintainable code. And remember, just like with any powerful tool, it’s essential not to go overboard and use hooks where they make sense. Happy coding!

Check my articles:

--

--