Sitemap
Anthony Eze

Anthony Eze

Understanding React Hooks: A Practical Guide

3 min readMar 18, 2026

--

Introduction

React is a popular open-source library for building user interfaces more efficiently than traditional tools like vanilla JavaScript or jQuery. One of React game-changing features is Hooks, introduced in React 16.8. Hooks allow developers to use state and other React features without writing class components. This simplifies code and speeds up development.

In this article, we explore six key React hooks: useState, useEffect, useContext, useRef, useCallback, and useMemo. We'll explain what they do, why they are useful, and how to implement them with practical examples.

1. useState: The Foundation of Dynamic Components

useState allows functional components to store and manage state. State enables a component to "remember" information and respond to user interactions. Changes in state or props trigger a re-render, allowing the UI to update dynamically.

Example:

import { useState } from 'react';

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

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Add 1</button>
<button onClick={() => setCount(count - 1)}>Decrease 1</button>
</div>
);
}

Explaination

a. Import useState from React.

b. Initialize state: const [count, setCount] = useState(0).

Here: count → current value

setCount → function to update the value

0 → initial state

c. Updating state: Call setCount(newValue).

Best Practice: Use the callback version to ensure updates are based on the latest state:

setCount(prevCount => prevCount + 1);

This avoids issues when multiple updates happen quickly, such as repeated button clicks.

2. useEffect: Handling Side Effects

useEffect is used for side effects - operations that reach outside a component, like doesnt depends on react components:

  • Fetching data from an API
  • Manipulating the DOM directly
  • Setting timers with setTimeout or setInterval

Example:

import { useState, useEffect } from 'react';

function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Count has changed!', count);
}, [count]);
}

Explanation:

  • The first argument: callback to run your effect.
  • The second argument: dependency array, which tells React when to run the effect.

Cleanup Function: Sometimes, effects need cleanup when a component unmounts:

useEffect(() => {
const timer = setInterval(() => console.log('Tick'), 1000);
return () => clearInterval(timer); // cleanup
}, []);

3. useContext: Avoiding Prop Drilling

useContext helps manage state across multiple levels of components without passing props manually at every level.

Example:

import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Navbar />
</ThemeContext.Provider>
);
}
function Navbar() {
const theme = useContext(ThemeContext);
return <h1>Theme: {theme}</h1>;
}

Explanation:

  • Provider makes the value available to all children.
  • useContext allows a child to read the value directly, avoiding prop drilling.

4. useRef: Persistent Values and DOM Access

useRef creates a mutable object whose .current property can hold a value persistently across renders.

Get Anthony Eze’s stories in your inbox

Join Medium for free to get updates from this writer.

Two main use cases:

a) Accessing DOM elements

import React, { useRef } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => inputRef.current.focus();
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}

b) Storing mutable values

function Counter() {
const [count, setCount] = useState(0);
const renders = useRef(0);
renders.current += 1;
return (
<div>
<p>Count: {count}</p>
<p>Renders: {renders.current}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

Key differences from state:

  • Updating a ref does not trigger a re-render.
  • Ref updates are synchronous, while state updates are asynchronous.

5. useCallback: Memoizing Functions

useCallback caches a function so it does not get recreated on every render, which is especially useful when passing functions as props to child components.

import { useCallback } from 'react';
import Child from './Child';

function App() {
const handleClick = useCallback(() => {
console.log('Button clicked!');
}, []);
return <Child onClick={handleClick} />;
}
  • The function is only recreated if dependencies change.
  • Helps prevent unnecessary child re-renders.

6. useMemo: Memoizing Values

useMemo caches the result of a calculation instead of a function, preventing expensive recalculations.

import { useMemo } from 'react';

function App({ num }) {

const doubled = useMemo(() => num * 2, [num]);

return <p>Doubled: {doubled}</p>;
}
  • Use useMemo for performance optimization when computing values based on changing inputs.

Conclusion

React Hooks provide powerful ways to manage state, side effects, context, and performance in functional components.

  • useState → dynamic component state
  • useEffect → side effects and cleanup
  • useContext → shared state across components
  • useRef → persistent mutable values or DOM access
  • useCallback → memoize functions
  • useMemo → memoize computed values

Understanding these hooks gives you the ability to write cleaner, faster, and more maintainable React applications.

--

--