Upgrade the React Skills with These 5 Hooks Principles

Ashish Misal
4 min readSep 2, 2024

--

As we head towards 2025, React remains a top choice for front-end development, thanks to its large community and widespread adoption. One of the most impactful features of React is hooks, which have transformed the way we handle state and side effects in our applications. However, hooks are often overused or misused. To help you use hooks more effectively, here are five important principles to keep in mind:

1. Not Every Function Needs to be a Hook

Hooks are incredibly useful, but that doesn’t mean every function should be a hook. You should use hooks like `useState` or `useEffect` only when necessary. A function isn’t a hook unless it involves state logic or effects. For instance, if a custom function doesn’t contain any state management or side effects, then it’s just a regular function, not a hooks

// Regular function
function formatPrice(price) {
return `$${price.toFixed(2)}`;
}

// Custom hook
function useBoolean(initialValue = false) {
const [value, setValue] = useState(initialValue);

const toggle = useCallback(() => {
setValue((prev) => !prev);
}, []);

return [value, toggle];
}

In this example, `formatPrice` is a regular function because it does not involve state or effects. `useBoolean`, on the other hand, is a custom hook because it manages state with `useState`.

2. Understand Re-renders

Changing state in React triggers a re-render. Knowing how the rendering process works helps you manage component updates more efficiently and avoid unnecessary re-renders that can affect your app's performance. For example, when you change a component’s state, React doesn’t immediately update the DOM. Instead, it schedules a re-render and only applies the minimal changes required after recalculating the component’s output. This understanding can help you manage the component’s lifecycle more effectively.

Example:

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

const handleClick = () => {
setCount(count + 1);
console.log('Clicked:', count); // Logs the previous count
};

return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}

In this example, when the button is clicked, the `handleClick` function is called. It sets a new state, which triggers a re-render, but the `console.log` inside `handleClick` still logs the previous state value because the state update is asynchronous.

3. `useState` Isn’t Always the Right Choice

While `useState` is the go-to for most state management needs, it's not always the best choice. For more complex state logic, consider using `useReducer`. This hook is better suited for handling more intricate state transitions and can make your code easier to manage. If you need to hold a value across re-renders without causing a re-render, `useRef` might be the better option. This hook can be particularly useful for keeping a reference to a DOM element or storing data that doesn’t need to trigger re-renders.

Example:

// Using useReducer for complex state
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 (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}

// Using useRef for persistent value
function Timer() {
const timerId = useRef(null);

useEffect(() => {
timerId.current = setInterval(() => {
console.log('Tick');
}, 1000);

return () => clearInterval(timerId.current);
}, []);

return <div>Timer is running. Check the console for ticks.</div>;
}

In the `Counter` component, `useReducer` is used to manage state with multiple actions. In the `Timer` component, `useRef` is used to persist the timer ID across renders without causing any re-renders.

4. Use `useEffect` Sparingly

`useEffect` is a powerful hook that synchronizes a component with external systems, such as fetching data from an API. However, overusing `useEffect` can lead to bugs and performance issues. Before adding an effect, make sure it’s necessary for the component’s functionality. For example, you don’t need to use `useEffect` for every change in state or props. Consider alternative methods or restructure your components to minimize the use of `useEffect`.

Example:

// Correct use of useEffect
function DataFetcher() {
const [data, setData] = useState(null);

useEffect(() => {
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
}

fetchData();
}, []); // Empty dependency array means this runs once when the component mounts

return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

// Unnecessary use of useEffect
function Component() {
const [text, setText] = useState('Hello');

// Bad practice: unnecessary effect
useEffect(() => {
setText(text + ' World');
}, [text]); // This causes an infinite loop
}

In `DataFetcher`, `useEffect` is used correctly to fetch data only once when the component mounts. In the `Component` example, `useEffect` is used unnecessarily, which results in an infinite loop.

5. Don’t Underestimate `useRef`

`useRef` is an underrated hook but incredibly useful. It allows you to persist values across renders without causing a re-render. This can be particularly handy when you need to reference DOM elements directly or maintain values that don’t affect the visual output, like timers or counters. By using `useRef` wisely, you can reduce unnecessary re-renders and improve your application's performance.

Example:

function FocusInput() {
const inputRef = useRef(null);

const focusInput = () => {
inputRef.current.focus();
};

return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus the input</button>
</div>
);
}

function PersistentCounter() {
const countRef = useRef(0);

const increment = () => {
countRef.current += 1;
console.log('Current count:', countRef.current);
};

return (
<div>
<button onClick={increment}>Increment Count</button>
</div>
);
}

Conclusion

By following these principles and leveraging the examples provided, you can make your React applications more efficient and maintainable. Hooks are powerful tools that, when used correctly, can greatly enhance your development process. However, it's important to use them thoughtfully and understand the nuances of how they work within the React ecosystem.

Connect with Me on LinkedIn

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Ashish Misal
Ashish Misal

Written by Ashish Misal

Software Developer | Expert in JavaScript, Node.js, React, MERN Stack | Building scalable apps | Mentor for developers | Passionate about innovation

No responses yet

Write a response