Mastering React Hooks: A Comprehensive Guide to Functional Components
Tags: react, webdev, javascript, frontend
Published: 2024-04-19 20:31:05

---
canonical_url: https://antondevtips.com/blog/mastering-react-hooks-a-comprehensive-guide-to-functional-components
---

## Introduction

The introduction of **Hooks** in React 16.8 significantly changed the coding style for building React components. 
**Hooks** allow to control component's state, lifecycle and offer a more powerful and flexible way to create and manage functional components.

**Prior to Hooks**, stateful components required the use of classes, which could complicate the codebase and increase a learning curve for new frontend developers. 
Hooks provide a way to "hook into" React state and lifecycle features from function components.
In this blog post you will learn the essentials and the most common **React Hooks**.

## useState hook

**useState** allows to save and update data inside the functional component that persists through multiple component re-renders.

```tsx
import { useState } from "react";

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

    return (
        <div>
            <p>You have {count} coffee(s)</p>
            <button onClick={() => setCount(count + 1)}>
                + 1 coffee
            </button>
            <button onClick={() => setCount(count - 1)}>
                - 1 coffee
            </button>
        </div>
    );
}
```

Here we define a **count** variable inside a **CoffeeCounter** component. 
The **useState** hook contains a variable itself and a function that allows to a change value of the variable. 
Everytime a **setCount** function is called - a component is rendered with a new value of **count** variable.


In this example we created a simple counting component that increases and decreases count by 1 every time you press a corresponding button.

In some use cases you might not have the actual value of **count** variable, for example in callbacks. 
In this case you can use another form of **setCount** function that has a previous value of the variable:

```tsx
<button onClick={() => setCount(prev => prev + 1)}>
    Click Me
</button>
```

In the **useState** hook you can store any types: primitive variables, arrays, objects. Let's have a look on how to store objects in the component's state:
```tsx
export const PersonData = () => {
    const [person, setPerson] = useState<Person>({ name: '', age: 0 });
    
    const savePerson = () => {
        setPerson({ name: "Anton", age: 20 });
    };

    return (
        <div>
            <div>{person.name} ({person.age} years)</div>
            <button onClick={() => savePerson()}>
                Set person data
            </button>
        </div>
    );
};
```

## useEffect hook
**useEffect** hook allows to hook into component's life cycle and add side effects such as API calls, calculations, manual DOM updates, etc.

```tsx
import { useEffect, useState } from "react";

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

    useEffect(() => {
        console.log("Page loaded");
    }, []);
    
    useEffect(() => {
        console.log("Count updated: ", count);
    }, [count]);

    return (
        // Rendering code remains the same
    );
}
```

**useEffect** hooks has 2 parameters: a function that performs a side effect and an array of tracked variables. 
When a variable value changes - a **useEffect** hook is triggered. The first hook has an empty braces **[ ]** without parameters, it means that this effect will be called **only once** after a component is loaded.
This is a perfect place to load some data from the API when page is loaded. 
Second hook is called only when **count** variable is changed. In the **CoffeeCounter** component logs a message to console when a page is loaded and after clicking any button that changes the **count** value. 


## useReducer hook
**useReducer** is helpful when you need to perform complex state updates. 
Let's rewrite our **CoffeeCounter** component and replace **useState** hook with **useReducer**:

```tsx
import {useReducer} from "react";

type CounterState = {
  count: number  
};

type CounterAction = {
    type: string
};

function reducer(state: CounterState, action: CounterAction) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            throw new Error();
    }
}

export const ReducerCounter = () => {
    const [state, dispatch] = useReducer(reducer, { count: 0 });

    return (
        <div>
            <p>You have {state.count} coffee(s)</p>
            <button onClick={() => dispatch({ type: 'increment' })}>
                + 1 coffee
            </button>
            <button onClick={() => dispatch({ type: 'decrement' })}>
                - 1 coffee
            </button>
        </div>
    );
}
```

Instead of just calling a set function that sets a new value, a dispatch method from **useReducer** is called that can perform a complex state manipulations.

Every state update using a **dispatch** function requires a name of the action type, in our example we have 2 actions: increment and decrement. 
After dispatch function is executed, a **reducer** function comes into play and performs state updates depending on a given action.

## useCallback and useMemo

**useCallback** and **useMemo** help in optimizing performance by memoizing functions and values respectively, avoiding unnecessary re-renders.
```tsx
import React, {useCallback, useMemo, useState} from "react";

type ExpensiveComponentProps = {
    compute: (n: number) => number;
    count: number;
}

const ExpensiveComponent: React.FC<ExpensiveComponentProps> = ({ compute, count }) => {
    const result = useMemo(() => compute(count), [compute, count]);
    return <div>Result: {result}</div>;
};

const ParentComponent: React.FC = () => {
    const [count, setCount] = useState<number>(0);

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

    return (
        <div>
            <ExpensiveComponent compute={(n: number) => n * 2} count={count} />
            <button onClick={increment}>Increment</button>
        </div>
    );
};

export { ExpensiveComponent, ParentComponent };
```

**ExpensiveComponent** accepts two props: compute function and a count number. 
The component uses the **useMemo** React hook to memorize the result of calling the **compute()** function with the count argument. 
This result will only be re-calculated if the compute function or the count prop changes.

**useCallback** is used to prevent unnecessary re-renders of components. 
Without **useCallback**, function props passed down to child components can cause them to re-render whenever the parent component renders, even if the passed functions didn't change logically. 
This is because a new function is created on each render and JavaScript can't perform function equality checks.

In this example, the count state variable in the **ParentComponent** is incremented by 1 each time the button is clicked.
When the **ExpensiveComponent** receives a prop function compute - it doubles the input. So each time Expensive component receives a counter variable - it doubles the result. 
On a screen you will see the following results: 0, 2, 4, 6, 8 (doubled from 0, 1, 2, 3, 4 respectively) and so on.

## Summary

**React Hooks** is a modern way you build components in React, offering a more intuitive and functional approach to state management and side effects. 
By mastering Hooks, you can simplify your components, make your code more readable and maintainable. In this blog post we explored the most common React Hooks.


- The **useState** hook allows data to be saved and updated within functional components that persist through multiple component renders.
- The **useEffect** hook adds side effects such as API calls and calculations, to the component's lifecycle.
- The **useReducer** hook provides a more flexible method to manage complex state logic in a component, acting similar to Redux’s reducer for local state management.
- The **useCallback** hook memorizes a callback function and only changes when its dependencies do, hence preventing unnecessary re-renders in child components expecting this callback as a prop.
- The **useMemo** hook is used to memorize expensive computations and only recompute them when necessary, optimizing performance by avoiding unnecessary calculations.

Hope you find this blog post useful. Happy coding!

_Originally published at_ [_https://antondevtips.com_](https://antondevtips.com/blog/mastering-react-hooks-a-comprehensive-guide-to-functional-components)_._

### After reading the post consider the following:

- [Subscribe](https://antondevtips.com/blog/mastering-react-hooks-a-comprehensive-guide-to-functional-components#subscribe) **to receive newsletters with the latest blog posts**
- [Download](https://github.com/AntonMartyniuk-DevTips/dev-tips-code/tree/main/frontend/react/hooks-guide) **the source code for this post from my** [github](https://github.com/AntonMartyniuk-DevTips/dev-tips-code/tree/main/frontend/react/hooks-guide) (available for my sponsors on BuyMeACoffee and Patreon)

If you like my content —  **consider supporting me**

Unlock exclusive access to the source code from the blog posts by joining my **Patreon** and **Buy Me A Coffee** communities!

[![](https://cdn-images-1.medium.com/max/233/1*GSZFx05RDUWr5pn_E9bK8w.png)](https://www.buymeacoffee.com/antonmartyniuk)

[![](https://cdn-images-1.medium.com/max/229/1*8tJk9eYCJrkzoAJw2cILSA.png)](https://www.patreon.com/bePatron?u=73769486)