React Hooks Deep Dive

A comprehensive guide to React Hooks. Learn how to leverage hooks to build powerful and reusable components in React.


Mario Yonan


7 mins

React Hooks Deep Dive post image

Introduction to React hooks

What are React Hooks?

React Hooks are a feature introduced in React 16.8 that allows you to use state and other React features without writing a class. They provide a more concise and readable way to manage state and side effects in functional components.

Why Use React Hooks?

  • Hooks enable functional components to have state and lifecycle methods, making them more powerful and flexible.
  • They promote code reusability by allowing logic to be encapsulated into reusable functions.
  • Hooks simplify complex component hierarchies by reducing the need for wrapper components and higher-order components.

Benefits of React Hooks

  1. Simplicity: Hooks simplify component logic by allowing you to separate concerns into smaller, composable functions.
  2. Code Reusability: Hooks encourage the creation of reusable logic that can be shared across multiple components.
  3. Improved Performance: Hooks can lead to better performance optimizations by reducing unnecessary re-renders.
  4. Easier Testing: Hooks make it easier to test component logic in isolation without relying on complex testing setups.

Rules of Hooks

Before delving into specific examples and use cases, it’s essential to understand the rules of using React hooks to ensure code consistency and prevent unexpected behavior. Adhering to these rules will help maintain the integrity of your components and ensure that hooks are used correctly:

  1. Only Call Hooks at the Top Level: Hooks should only be called at the top level of your functional components or other custom hooks. Avoid calling hooks inside loops, conditions, or nested functions to ensure they are called consistently on every render.
  2. Call Hooks from React Functions: Hooks should only be called from React function components or custom hooks. Avoid calling hooks from regular JavaScript functions, classes, or asynchronous callbacks.
  3. Only Call Hooks from React Components: Hooks should only be called from within React components or custom hooks. Avoid calling hooks from regular JavaScript functions, as it can lead to unpredictable behavior.

Adhering to these rules ensures that your components are predictable, maintainable, and compatible with future updates to React.

useState hook for State Management

What is the useState Hook?

The useState Hook is a function provided by React that allows functional components to manage state. It returns a stateful value and a function to update that value, similar to this.state and this.setState in class components.


The useState Hook takes an initial state as its argument and returns an array with two elements: the current state value and a function to update the state.

const [state, setState] = useState(initialState);


import React, { useState } from 'react';

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

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

Updating State

To update the state with the useState Hook, you call the state updater function returned by useState, passing it the new state value.

setCount(count + 1);

Functional Updates

You can also use functional updates with the useState Hook, especially when the new state value depends on the previous state.

setCount((prevCount) => prevCount + 1);

When to Use useState

  • Managing local component state.
  • Storing data that may change over time within a component.

useEffect Hook for Side Effects

What is the useEffect Hook?

The useEffect Hook is used in functional components to perform side effects. It allows you to perform operations such as data fetching, subscriptions, or manually changing the DOM after the component has rendered.


The useEffect Hook accepts two arguments: a function containing the side effect, and an optional array of dependencies. The function will run after every render unless specified otherwise.

useEffect(() => {
  // Side effect code here
}, [dependencies]);


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

const ExampleComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  }, [count]); // Only re-run the effect if count changes

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

Cleanup Function

You can return a cleanup function from the useEffect Hook, which will be called before the component unmounts or before the effect runs again.

useEffect(() => {
  const subscription = subscribe();
  return () => {
    // Cleanup function here
}, []);


By providing a dependency array, you can specify when the effect should be re-run. If the array is empty, the effect only runs once after the initial render.

Common Use Cases

  • Fetching data from an API.
  • Subscribing to external events.
  • Updating the DOM based on component state.

useContext Hook for State Management

What is the useContext Hook?

The useContext Hook provides a way to consume context within functional components in React. Context allows you to pass data through the component tree without having to pass props down manually at every level.


The useContext Hook accepts a context object (created with React.createContext) and returns the current context value for that context.

const value = useContext(MyContext);


import React, { useContext } from 'react';

const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme }}>I am styled by theme context!</button>;

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedButton />

Usage Guidelines

  • Use useContext when you need to access the same context in multiple components without needing to pass props down manually.
  • Make sure to provide the appropriate context value using Context.Provider higher in the component tree.

Best Practices

  • Avoid excessive nesting of providers and consumers. Place providers at the top level of your application and consumers where they are needed.
  • Consider creating separate context objects for different types of data to keep your code organized.

Common Use Cases

  • Theming: Provide a theme to your entire application.
  • Authentication: Pass user authentication status or user details throughout your app.
  • Localization: Pass the current language or locale to all components.

Creating Custom Hooks

What are Custom Hooks?

Custom Hooks are JavaScript functions that utilize React’s Hook API. They allow you to extract reusable logic from components, making your code more modular and easier to maintain.

Benefits of Custom Hooks

  • Reusability: Custom Hooks allow you to encapsulate logic and reuse it across multiple components.
  • Separation of Concerns: They help separate concerns by abstracting away complex logic from components.
  • Simplicity: Custom Hooks promote cleaner and more readable component code by keeping logic separate from UI concerns.


To create a custom hook, simply define a JavaScript function prefixed with use. Inside the function, you can use built-in Hooks or other custom Hooks.

import { useState, useEffect } from 'react';

function useCustomHook(initialValue) {
  const [value, setValue] = useState(initialValue);

  useEffect(() => {
    // Optional effect logic...
  }, [value]);

  return value;


import { useState } from 'react';

function useCounter(initialCount, step) {
  const [count, setCount] = useState(initialCount);

  const increment = () => setCount(count + step);
  const decrement = () => setCount(count - step);

  return { count, increment, decrement };


You can use custom hooks just like built-in hooks within functional components.

function CounterComponent() {
  const { count, increment, decrement } = useCounter(0, 1);

  return (
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>

Best Practices

  • Name Convention: Prefix custom hook names with use to indicate that they follow the Hook API.
  • Single Responsibility: Aim for single-responsibility custom hooks to keep them focused and reusable.
  • Dependency Arrays: Use dependency arrays in useEffect within custom hooks to manage side effects.

Common Use Cases

  • Data Fetching: Abstracting API calls and data fetching logic.
  • Form State Management: Managing form state and validation logic.
  • Animation: Creating reusable animation effects.
  • Event Listeners: Handling event listeners and subscriptions.

Best Practices

  1. Understand the React Component Lifecycle: Have a solid understanding of the React component lifecycle to know when and where to use hooks effectively. Hooks like useState and useEffect mimic class component behavior but with a more concise syntax.
  2. Follow the Rules of Hooks: Adhere to the Rules of Hooks to ensure hooks are only called from within functional components or custom hooks. Avoid calling hooks conditionally or within nested functions.
  3. Keep Hooks Near Related Logic: Place hooks near related logic within your functional components to maintain readability and organization. This makes it easier for other developers to understand the component’s behavior.
  4. Use Memoization for Performance: Utilize memoization techniques such as useMemo and useCallback to optimize performance by memoizing expensive calculations or preventing unnecessary re-renders.
  5. Leverage Custom Hooks for Reusability: Abstract shared logic into custom hooks for reusability across multiple components. Custom hooks allow you to encapsulate complex behavior and promote a modular codebase.

Common Pitfalls

  1. Missing Dependency Arrays in useEffect: Forgetting to include dependency arrays in useEffect can lead to bugs and unexpected behavior. Always specify dependencies to ensure the effect runs only when necessary.
  2. Mutating State Directly: Avoid mutating state directly when using hooks like useState or useReducer. Instead, use the setter function provided by these hooks to update state immutably.
  3. Accidental Infinite Loops: Be cautious when using hooks like useEffect to prevent accidental infinite loops. Ensure that the dependencies array is properly defined and that side effects are handled appropriately.
  4. Overusing useState for Complex State: Reserve useState for managing simple, independent state variables. For complex state management, consider using useReducer or custom hooks to maintain a more organized codebase.
  5. Not Cleaning Up Effects: Remember to clean up any side effects in the useEffect hook by returning a cleanup function. This prevents memory leaks and ensures that resources are properly released.


In this deep dive into React hooks, we’ve explored the fundamental hooks provided by React and learned how they revolutionize state management and side effects handling in functional components.

From useState for managing component state to useEffect for handling side effects, useContext for global state management, and custom hooks for code reusability, React hooks offer a powerful and intuitive way to build modern web applications.

By mastering React hooks, you can streamline your development workflow, write cleaner and more maintainable code, and unlock new possibilities for building dynamic and responsive user interfaces.

React Hooks

Subscribe to my newsletter

Coming soon!

← All Posts