React hooks with examples

supraja
12 min readDec 29, 2023

--

React hooks
image credit: link

React hooks are the javaScript functions that manage the state’s behaviour and side effects by isolating them from a component.

Using react hooks, we hook into react lifecycle. The following are the stages of react lifecycle in simple terms, where hooks come into picture:
1. When component mounts
2. When state changes
3. When props changes
4. When context or store changes
5. When component unmounts

For example,
at stage 1, 2 -> useState, useEffect are used
at stage 4 -> useContext, useReducer are used

You can refer to my article on react lifecycle to learn about lifecycle phases and also the article on side effects :)

image credit: https://wavez.github.io/react-hooks-lifecycle/

React provides a bunch of standard in-built hooks:

  1. useState: To manage states. Returns a stateful value and an updater function to update it.
  2. useEffect: To manage side-effects like API calls, subscriptions, timers, mutations, and more.
  3. useCallback: It returns a memorized version of a callback to help a child component not re-render unnecessarily.
  4. useMemo: It returns a memoized value that helps in performance optimizations.
  5. useContext: To return the current value for a context.
  6. useReducer: A useState alternative to help with complex state management.
  7. useRef: It returns a ref object with a .current property. The ref object is mutable. It is mainly used to access a child component imperatively.
  8. useLayoutEffect: It fires at the end of all DOM mutations. It's best to use useEffect as much as possible over this one as the useLayoutEffect fires synchronously.

1. useState:

Used to declare and track the component’s state variable’s value. State is the data of component we deal with, and that data holds certain values which is used in JSX to render. When there is a change in state, react re-renders the part(JSX) that got changed, it means that the component function gets executed again.

Accepts an optional argument, which can be a valid javascript value, to set initial value to state variable.

Returns array of two values. The first one is the current value of state variable. The second one is the method to update the state variable

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

NOTE: We don’t update state variable directly, but by using setState method, because react uses virtual DOM and it updates virtual DOM, it doesn’t update actual DOM. Then differentiation algorithm is applied between these two DOMs and only the changed portion is re-rendered by react.

Example:

import React,{ useState } from 'react';

function StateHookExample() {
const white = '#ffffff';
const black = '#000000';
const [isWhite, setIsWhite] = useState(true);
const [hello, setHello] = useState('');
return (
<div style={{backgroundColor: isWhite ? white : black}}>
<button onClick={() => {
setIsWhite( (prevState) => !prevState );
}}>Toggle background</button>
<p style={{color: !isWhite ? white : black}}>{hello}</p>
<button onClick={() => {
setHello('Hello!');
}}>Say hello</button>
</div>
);
}
export default StateHookExample;

This example shows two state variables, isWhite and hello.
- On clicking on ‘Toggle background’ button, we update(toggle) state variable(isWhite), based on previous state value.
- On clicking on ‘Say hello’ button, we just update state variable without considering previous state value.

2. useEffect:

useEffect is a React Hook used for handling side effects in functional components. It replaces lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount in class components. It handles tasks such as data fetching, DOM manipulation, and subscriptions.

Accepts a function which has effect code and optionally returns method which has cleanup code, and an optional argument which is a dependency array.

Basic Syntax:

import React, { useEffect } from 'react';

function MyComponent() {
useEffect(() => {
// Effect code here
return () => {
// Cleanup code (optional)
};
}, [/* Dependency array */]);

return (
// JSX for the component
);
}

Key Concepts:

(1) Effect Code:

  • Code inside the useEffect function runs after the component renders.

(2) Dependency Array:

  • An optional array specifying dependencies for the effect. If dependencies change, the effect re-runs.
  • Omitting the array runs the effect on every render.

(3) Cleanup Function:

  • The optional cleanup function runs when the component unmounts or when dependencies change before the next effect runs.

The dependency array and cleanup function offer control over when the effect runs and how to clean up resources.

Example:

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

function ExampleComponent() {
// State for managing count
const [count, setCount] = useState(0);

// State for managing data fetched from an API
const [data, setData] = useState(null);

// Effect for document title update on count change
useEffect(() => {
document.title = `Count: ${count}`;

// Cleanup function (optional)
return () => {
document.title = 'React App';
};
}, [count]);

// Effect for fetching data from an API on component mount
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
}
};

fetchData();
}, []); // Run once on mount

// JSX for the component
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>

{data ? (
<div>
<p>Data from API: {data}</p>
</div>
) : (
<p>Loading data...</p>
)}
</div>
);
}

export default ExampleComponent;

In this example:

  • The component has state variables count and data.
  • The first useEffect updates the document title when count changes(since it is mentioned in dependency array), with an optional cleanup function to reset the title.
  • The second useEffect fetches data from an API when the component mounts (runs once on mount).

Performance Improvements of useCallback and useMemo over useEffect:

useCallback and useMemo are hooks provided by React to optimize the performance of functional components by memoizing functions or values. While useEffect is primarily used for managing side effects and lifecycle events, useCallback and useMemo specifically address scenarios where you want to optimize the rendering process.

3. useCallback:

The useCallback hook is used to memoize functions. It returns a memoized version of the callback function that only changes if one of the dependencies has changed.

Syntax:

//Accepts function and dependency array
useCallback(fn, []);
// It returns fn, when [] changes

Example without useCallback:

import { useState } from "react";
import GreetingBox from "./GreetingBox";

function App() {
const [name, setName] = useState("");
const [counter, setCounter] = useState(0);

const getGreeting = () => {
return `Hello ${name}!`;
};

return (
<div className="App">
<input
placeholder="Enter your name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<GreetingBox getGreeting={getGreeting} />

{counter}
<button onClick={() => setCounter(counter + 1)}>+ by 1</button>
</div>
);
}
export default App;
import { useState, useEffect } from "react";

const GreetingBox = ({ getGreeting }) => {
const [greeting, setGreeting] = useState();

useEffect(() => {
setGreeting(getGreeting());
console.log("GreetingBox: useEffect");
}, [getGreeting]);

return (
<div>
<h1>{greeting}</h1>
</div>
);
};

export default GreetingBox;

Why does the above example cause performance issue?

In the given example, the performance issue arises because the getGreeting function, passed as a prop to the GreetingBox component, is recreated on every render of the parent component (App). This constant recreation of the function instance leads to unnecessary re-renders of the GreetingBox component, even when the input value (name) remains unchanged.

As a result, when the counter variable changes in the parent component, causing a re-render, the useEffect in the GreetingBox component is triggered due to the changed reference of the getGreeting function. This behavior is not ideal, as the function's logic hasn't changed, and it creates a performance overhead by unnecessarily re-executing the useEffect and updating the state in the child component.

This issue can be addressed by using useCallback to memoize the getGreeting function, ensuring that its reference remains consistent across renders unless its dependencies (name in this case) change.

Example withuseCallback:

Note: Only parent component(App) is updated, to show optimisation. The child component is same as above.

import { useState, useCallback } from "react";
import GreetingBox from "./GreetingBox";

function App() {
const [name, setName] = useState("");
const [counter, setCounter] = useState(0);
// Here, the useCallback is used to solve performance issue
const getGreeting = useCallback(() => {
return `Hello ${name}!`;
}, [name]);

return (
<div className="App">
<input
placeholder="Enter your name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<GreetingBox getGreeting={getGreeting} />

{counter}
<button onClick={() => setCounter(counter + 1)}>+ by 1</button>
</div>
);
}

export default App;

In the revised example, the performance issue is addressed by using useCallback to memoize the getGreeting function. By providing the dependency array [name], the function is now only recreated when the name variable changes, ensuring consistent references across renders when the name remains unchanged.

This optimization prevents unnecessary re-renders of the GreetingBox component when unrelated state changes occur in the parent component (App). The useCallback hook efficiently memoizes the function, promoting better performance by avoiding redundant function recreations and subsequent re-executions of the effect in the child component.

4. useMemo:

The useMemo hook is used to memoize values. It returns a memoized version of the value that only recomputes when one of the dependencies has changed.

Syntax:

//Accepts function and dependency array
useMemo(fn, []);
// It executes fn() and returns the value, when [] changes

Example without useMemo:

import { useState, useMemo } from "react";

const getAgeAnalytics = (data) => {
// Do something dramatically slow here!
};

const UserAnalytics = () => {
const [users, setUsers] = useState([]);

const ageData = getAgeAnalytics(users);

return <div>{ageData}</div>;
};

In the above example without useMemo, the getAgeAnalytics function is invoked on every render of the UserAnalytics component, potentially leading to redundant and slow computations even when the users data remains unchanged.

Example with useMemo:

import { useState, useMemo } from "react";

const getAgeAnalytics = (data) => {
// Do something dramatically slow here!
};

const UserAnalytics = () => {
const [users, setUsers] = useState([]);

const ageData = useMemo(() => getAgeAnalytics(users), [users]);

return <div>{ageData}</div>;
};

In contrast, the example with useMemo optimizes performance by memoizing the result of getAgeAnalytics(users). The function is only recomputed when the users dependency changes, preventing unnecessary recalculations on every render. This improves efficiency and responsiveness, especially in scenarios where the computation is resource-intensive.

NOTE: Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.

Strategically employing useCallback and useMemo can reduce the number of unnecessary renders in React components, enhancing overall efficiency. However, it's crucial to use them judiciously and only in situations where their optimization benefits are needed, as excessive use can introduce unnecessary complexity to the codebase.

5. useContext:

useContext is a React hook that allows components to consume values from the React Context without prop drilling. It provides a way to access the value of a context directly within a functional component, eliminating the need to pass props through intermediary components.

How useContext Works:

(1) Create Context:

  • First, a context is created using React.createContext.
const MyContext = React.createContext();
// we can pass initial value to this createContext() method optionally,
// so that context will be initialized with that value
// and we can also override the value later

(2) Provide Context Value:

  • The context provider wraps the component tree and sets the context’s value.
<MyContext.Provider value={/* some value */}>
{/* Component tree */}
</MyContext.Provider>

(3) Consume Context Value:

  • Inside functional components, the useContext hook is used to access the context's current value.
const contextValue = useContext(MyContext);

Problems Solved by useContext:

(1) Prop Drilling:

  • useContext eliminates the need for prop drilling by providing a direct way to access context values, making the code more readable and maintainable.

(2) Component Decoupling:

  • Components no longer need to be aware of or pass along props that are not directly relevant to their functionality. This reduces coupling and enhances component isolation.

(3) Simplified State Management:

  • useContext is often used in conjunction with the Context API to simplify state management, allowing global state to be accessed and modified from any part of the component tree.

By using useContext, developers can create more modular and maintainable React applications, particularly when dealing with shared state or configuration values across different components.

6. useReducer:

useReducer is a React hook that is used for managing state in functional components. It is an alternative to useState and is particularly useful when dealing with complex state logic that involves multiple sub-values or when the next state depends on the previous one.

The primary motivation for using useReducer over useState arises when you have complex state logic that involves multiple actions or when the next state depends on the previous one. It can also be beneficial in scenarios where the state transitions are more predictable and involve complex logic, making it easier to manage in a reducer function.

Syntax of useReducer

Key points about useReducer:

(1) Arguments:

  • useReducer takes two arguments:
  • A reducer function responsible for updating the state based on actions.
  • An initial state that defines the initial value of the state.

(2) Reducer Function:

  • The reducer function is called with the current state and an action.
  • It examines the action type and returns a new state accordingly.

(3) Action Handling:

  • Actions are objects that describe state changes.
  • The reducer interprets these actions to determine how the state should be updated.

(4) State Update:

  • The useReducer hook returns the current state and a dispatch function.
  • Dispatching an action triggers the reducer, leading to a new state calculation.

Here’s an example to illustrate how useReducer works and how it can be used to manage state more effectively:

import React, { useReducer } from 'react';

// Reducer function
const counterReducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + action.payload };
case 'DECREMENT':
return { count: state.count - action.payload };
case 'RESET':
return { count: 0 };
default:
return state;
}
};

const Counter = () => {
// Initial state and dispatch function
const initialState = { count: 0 };
const [state, dispatch] = useReducer(counterReducer, initialState);

const handleIncrement = () => {
dispatch({ type: 'INCREMENT', payload: 1 }); // Payload of 1 (or any other value)
};

const handleDecrement = () => {
dispatch({ type: 'DECREMENT', payload: 1 }); // Payload of 1 (or any other value)
};

const handleReset = () => {
dispatch({ type: 'RESET' });
};

return (
<div>
<p>Count: {state.count}</p>
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
<button onClick={handleReset}>Reset</button>
</div>
);
};

export default Counter;

Let’s understand the above example step by step:
Reducer Function:

  • Handles actions (‘INCREMENT’, ‘DECREMENT’, ‘RESET’).
  • Modifies state based on the action type.
  • ‘INCREMENT’ and ‘DECREMENT’ actions include a payload for the amount.

Initial State and useReducer:

  • Initializes the state with a count of 0.
  • The useReducer hook is employed with the counterReducer and initialState, returning the current state (state) and a dispatch function.
  • useReducer manages state using the counterReducer.

Dispatching Actions:

  • Buttons trigger actions (‘INCREMENT’, ‘DECREMENT’, ‘RESET’).
  • dispatch sends actions to the reducer, with 'INCREMENT' and 'DECREMENT' actions having a payload for the amount.

Updating UI:

  • Displays the current count in the UI.
  • Clicking buttons triggers state updates through useReducer.

If you want to check another example on useReducer, please refer this

7. useRef:

useRef is a hook in React that returns a mutable object called a ref object. The ref object has a current property, which can hold a mutable value. The primary purpose of useRef is to persist values across renders without causing re-renders and to interact with and access the underlying DOM elements.

Syntax of useRef

useRef is commonly used for the following purposes:

  1. Preserving Values: The ref object allows values to persist across renders without triggering component updates.
  2. Accessing and Modifying DOM Elements: It enables interaction with DOM elements directly, facilitating actions like focusing on an input without causing re-renders.
  3. Maintaining Mutable Values: Helps in keeping mutable values that persist across renders without causing re-renders.
  4. Interaction with Class Components: Can be used to access the underlying instance of a class component from a functional component.
  5. Avoiding Closure Issues: Helps prevent closure-related issues in event handlers or asynchronous functions by ensuring a consistent reference.

Example 1:

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

const MyComponent = () => {
const myRef = useRef();

useEffect(() => {
// Access and manipulate the DOM element
myRef.current.focus();
}, []);

return <input ref={myRef} />;
};

export default MyComponent;

In this example, myRef is a ref object used to interact with the input element's DOM without causing a re-render. The current property of myRef holds the reference to the input element.

Example 2:

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

const Counter = () => {
const countRef = useRef(0);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
countRef.current = countRef.current + 1;
});
return(
<>
<input
type="text"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)} />
{countRef.current}
</>
)
};

export default Counter;

In this example, we are getting updated value of reference(countRef) when the re-render is due to another state variable(searchTerm) and reference(countRef) doesn’t cause rerendering. The value of reference(countRef) gets persisted and is never lost in component lifecycle.

useRef is useful when we may not need any kind of references in re-rendering part directly, but in useEffect we may want to get certain variables temporarily or preserve the variables temporarily over the re-renders.

If you want to learn about React custom hooks, refer to my article here for a clear and simple guide :)

--

--