DEV Community

Diana
Diana

Posted on • Edited on

The React Hooks Explained

React Hooks have simplified the way we write React components. With Hooks, we can manage state, handle side effects, and more, all within functional components. Introduced in React 16.8, Hooks allow us to use state and other React features in functional components, making our code simpler and more intuitive.

Image description

useState hook:

The most common hook is the useState hook. To understand useState, let's first try to understand what state is.

State refers to data that will change and needs to be tracked. So, let's see the structure of the useState hook:

Image description

n this example, we have two states: name on line 4 and displayName on line 5. name and displayName are the state variables that we are trying to track. setName and setDisplayName are the update functions that will be triggered when we want to update the value of the state variables.

We start with an empty string for each state variable (as we see in the parentheses of each state on lines 4 and 5—the starting value is optional, but note that you will have an empty state if you do not provide a default value).

By convention, the update function name will always have a prefix of set followed by the state variable name.

The state name is tracking what the user is typing in the input on line 17. Every time the value in the input changes, handleInputChange is triggered, and inside this function, we set name with the current value using setName.

Similarly, we have displayName, which is the name that will be displayed when the user clicks the button we created on line 18.

Every time we change the input, the name state is updated (I added a log so we can see it):
Image description

Only when we click on the button the displayName will be set to the current value we have in the name state. (what we see in the console is the name state changing)

Image description

useEffect hook:

The useEffect hook allows us to perform side effects in our application. In simple terms, side effects are things that happen as a result of some task we performed, like data fetching, updating the DOM, and more.

Let's understand the structure of the useEffect hook:

Image description

IMPORTANT: It's guaranteed that the useEffect will run at least once. If no dependency is given in the dependency array, the useEffect will run only one time when the component mounts.

Image description

Image description
In this example, the effect is a console.log of the state variable count. Every time count changes, the useEffect is triggered because we're listening to count in our dependency array.

Now, let's understand how the cleanup function works. To do this, we need to understand the lifecycle of the useEffect hook. Let's add another console.log for the cleanup function:

Image description

Image description

Now, when we run our code and change the count, the component is re-rendered then the cleanup function is running and then the code in the useEffect.

useRef hook:

The useRef hook is used to track or store values without triggering a re-render. The value from useRef will not be used in the return body. It takes an initial value and returns an object with a current property. Let’s look at an example of useRef to understand how it works. We’ll also compare it to the useState hook for better understanding.

We have here the example of the counter with the useRef hook:
Image description

When we increment the number, we will still see 0. This is because it's not causing a re-render, but in the console, we can see that the value is updated correctly:

Image description

So now we know: useRef is a hook that provides a way to persist values across renders without causing a re-render when those values change. This makes it ideal for scenarios where you want to store information that doesn't need to affect the rendering process, such as keeping track of previous values or interacting with DOM elements directly.
Additionally, useRef can be used to access DOM elements, making it useful for controlling focus, text selection, or animations in functional components. Since it persists through renders and doesn't cause re-renders, useRef is efficient for certain tasks where you don't need to update the UI.

useMemo hook:

The useMemo hook is commonly used to optimize performance by memoizing the result of a computation. It acts like a cache between re-renders, ensuring that expensive calculations are only re-executed when one of the dependencies changes. This helps avoid unnecessary recalculations on every re-render, making your component more efficient.

Image description
doubleNumber Function:

This function simulates an expensive operation with a loop (for(let i = 0; i<= 10000000000; i++) {}) and doubles the input number. It's a heavy computation meant to illustrate performance optimization.
Every time the component re-renders, running this function would normally cause a performance delay.

useMemo(() => doubleNumber(count), [count]): The useMemo hook memoizes (caches) the result of doubleNumber(count).

It only re-executes doubleNumber when the count state changes. This means if the component re-renders due to changes in the color state, the expensive doubleNumber function won't run again unless count has changed.

We can see that when we press on Increment the operation takes time, but the change color is immediate:

Image description

useCallback hook:

This hook is very similar to the useMemo hook. The difference between them is that useMemo takes a function and returns the result of that function, while useCallback takes a function and returns the function itself.

In the next example, we can see that when the component re-renders, the getNumbers function is recreated, even if only the color has changed. Therefore, each time the component updates, it generates a new function, even though the actual number did not change:

Image description

Image description

Image description

We can fix it with useCallback:

Image description

Image description

useContext hook:

The useContext hook in React is used to access values from a context without needing to pass props down through multiple layers of the component tree. It simplifies prop drilling by allowing components to subscribe to context values directly.

What is a Context?
Context is a way to share data (like a global variable) between components without having to pass it explicitly via props at every level. This is useful for things like themes, user authentication, or settings, where multiple components need access to the same data.

How useContext Works:

  1. Creating a Context:
    First, you create a context using React.createContext(). This creates a "context object" which holds the value you want to share.

  2. Providing the Context Value:
    Next, you wrap the components that need access to the context with the Context.Provider component. The Provider accepts a value prop, which is the data that will be available to all the components that use this context.

  3. Consuming the Context Value:
    Inside a child component, instead of passing props down, you can use the useContext hook to access the value of the context.

In the next example, we create a context in the Example component (line 5). Then, we wrap the User component with so that any children within the User component tree can access the context. Finally, the PersonalInfo and LocationInfo components consume the context and display the data using const user = useContext(UserContext).

Image description

Image description

Image description

Image description

Result:

Image description

useReducer hook:

The useReducer hook in React is a more advanced alternative to useState. It helps manage complex state logic in components, especially when the state depends on multiple actions or is composed of multiple values.
When you have complex state logic that involves multiple sub-values or state transitions or the next state depends on the previous state, you probably will need the useReducer.
Let's check the example and understand from it how it works:
In line 15 we declaring on the useReducer hook it takes 2 parameters that are mandatory:

  • reducer: A function that determines how the state should change, based on the action dispatched. We created the function in line 3-12.

  • { count : 0 }: The starting state of your component. Usually, we use an object because we have complex variables.

  • dispatch: A function you call with an action to trigger the reducer function. We have the dispatch in the increment and decrement functions.

Image description

In the reducer function, we always check the action type to determine the appropriate action. I used a switch statement, but you can also use if statements (although switch tends to be more readable).
Each time we click one of the buttons, the corresponding button's function is triggered, which calls the reducer function.

Top comments (0)