React Hooks Explained

Fredrick Mbugua
14 min readMar 22, 2023

In this article, we explore React Hooks, and their use and provide sample implementations for each.

React Hooks — Summary

  1. useState: The useState hook allows functional components to have state variables. This means that you can set and get the state of a component without the need for a class.
  2. useEffect: The useEffect hook lets you run side effects in functional components. Side effects include fetching data, updating the DOM, and subscribing to external APIs.
  3. useContext: The useContext hook lets you access data from the React context without needing to pass down props through every level of the component tree.
  4. useReducer: The useReducer hook lets you manage complex state in functional components. It works like the reducer function in Redux, allowing you to update state based on previous state and an action.
  5. useRef: The useRef hook provides a way to access the DOM node of a component or to store mutable values that persist between renders.
  6. useMemo: The useMemo hook allows you to memoize expensive computations so that they are only computed when necessary.
  7. useCallback: The useCallback hook lets you memoize functions so that they are only re-created when their dependencies change.
  8. useLayoutEffect: The useLayoutEffect hook is similar to useEffect, but it runs synchronously after all DOM mutations. This can be useful when you need to measure the size or position of a DOM node after it has been rendered.
  9. useImperativeHandle: The useImperativeHandle hook allows functional components to expose certain functions or methods to the parent component.
  10. useDebugValue: The useDebugValue hook lets you display custom labels for custom hooks in the React DevTools.

useState

useState is a built-in React hook that allows functional components to have state variables. In other words, it provides a way to add state to functional components without the need for a class.

Here’s how useState works:

Declare a state variable and initialize it to a default value using the useState hook.

const [stateVariable, setStateVariable] = useState(defaultValue);

useState returns an array with two elements: the current value of the state variable (stateVariable) and a function that can be used to update the state variable (setStateVariable).

To update the value of the state variable, call the setStateVariable function with the new value.

setStateVariable(newValue);

When the state variable is updated, React will automatically re-render the component with the new value.

Here’s an example of how to use useState to create a counter in a functional component:

import React, { useState } from 'react';

function Counter() {
// Declare a state variable called "count" and initialize it to 0
const [count, setCount] = useState(0);

// This function will be called when the button is clicked
function handleClick() {
// Call the setCount function to update the count variable
setCount(count + 1);
}

return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click me</button>
</div>
);
}

In the above example, we declare a state variable called count and initialize it to 0 using the useState hook. We also declare a function called handleClick which updates the count variable when the button is clicked.

Finally, we render the current value of the count variable along with a button that calls the handleClick function when clicked. When the button is clicked, the handleClick function updates the count variable using the setCount function, triggering a re-render of the component with the updated value.

useEffect

useEffect is a built-in React hook that allows functional components to perform side effects. Side effects include things like fetching data from an API, updating the DOM, and subscribing to external events.

Here’s how useEffect works:

Declare a side effect using the useEffect hook.

useEffect(() => {
// Side effect code goes here
});

useEffect takes a function as its argument. This function will be called after the component is rendered for the first time, and then after every subsequent re-render.

Inside the useEffect function, you can perform any side effects you need to perform. This might include fetching data from an API, updating the DOM, or subscribing to external events.

If your side effect requires cleanup, you can return a function from the useEffect function. This cleanup function will be called before the component is unmounted or before the side effect is run again.

useEffect(() => {
// Side effect code goes here

// Return a cleanup function if necessary
return () => {
// Cleanup code goes here
};
});

Here’s an example of how to use useEffect to fetch data from an API: Assume we have an API that returns Users data.

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

function UserList() {
// Declare a state variable called "users"
const [users, setUsers] = useState([]);

// Fetch the user data from the API and update the "users" state variable
useEffect(() => {
fetch('https://api.users.com/users')
.then(response => response.json())
.then(data => setUsers(data));
}, []);

return (
<div>
<h1>User List</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}

In the above example, we use useEffect to fetch data from an API and update the users state variable with the fetched data. We pass an empty dependency array as the second argument to useEffect, which means the side effect will only run once after the component is mounted.

We render the users state variable as a list of user names. When the component is rendered for the first time, the useEffect function is called to fetch the data and update the state variable. When the state variable is updated, React will automatically re-render the component with the updated data.

useContext

useContext is a built-in React hook that allows functional components to consume data from a React context.

Here’s how useContext works:

Create a React context using the createContext function.

const MyContext = createContext(defaultValue);

Wrap the part of your component tree that needs access to the context with a MyContext.Provider component.

<MyContext.Provider value={contextValue}>
// Children components go here
</MyContext.Provider>

MyContext.Provider takes a value prop which is the data you want to share with the child components. This value can be any JavaScript data type, including objects and functions.

In a child component that needs access to the context data, use the useContext hook to consume the data.

const contextValue = useContext(MyContext);

useContext takes a context object as its argument and returns the current context value.

Here’s an example of how to use useContext to consume data from a context:

import React, { useContext } from 'react';

// Create a new context object
const MyContext = createContext('default value');

function Parent() {
return (
// Wrap the child component with a MyContext.Provider component
<MyContext.Provider value="hello world">
<Child />
</MyContext.Provider>
);
}

function Child() {
// Use the useContext hook to consume data from the context
const contextValue = useContext(MyContext);

return (
<div>
<p>Context value: {contextValue}</p>
</div>
);
}

In the above example, we create a new context object called MyContext and set its default value to 'default value'. In the Parent component, we wrap the Child component with a MyContext.Provider component and set its value to 'hello world'.

In the Child component, we use the useContext hook to consume the context data and assign it to the contextValue variable. We then render the contextValue variable inside a paragraph element.

When the Parent component is rendered, the Child component is also rendered with the MyContext.Provider component wrapping it. The useContext hook in the Child component consumes the context data and assigns it to the contextValue variable, which is then rendered inside the paragraph element. The output will be "Context value: hello world".

useReducer

useReducer is a built-in React hook that allows functional components to manage complex state using a reducer function.

Here’s how useReducer works:

Define a reducer function that takes two arguments: the current state and an action object. The reducer function returns a new state based on the current state and the action.

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

Declare a state variable and a dispatch function using the useReducer hook. Pass in the reducer function and the initial state as arguments.

const [state, dispatch] = useReducer(reducer, initialState);

The state variable is the current state managed by the reducer function. The dispatch function is used to send an action object to the reducer function.

When the dispatch function is called with an action object, the reducer function is called with the current state and the action object. The reducer function returns a new state based on the current state and the action. The new state is then assigned to the state variable.

Here’s an example of how to use useReducer to manage state:

import React, { useReducer } from 'react';

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

function Counter() {
// Declare the state variable and the dispatch function
const [count, dispatch] = useReducer(reducer, 0);

// Event handlers for the buttons
const handleIncrement = () => dispatch({ type: 'increment' });
const handleDecrement = () => dispatch({ type: 'decrement' });

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

In the above example, we define a reducer function that takes a state and an action object, and returns a new state based on the action. We then declare a state variable called count and a dispatch function using the useReducer hook.

In the Counter component, we define two event handlers for the buttons: handleIncrement and handleDecrement. When the buttons are clicked, these event handlers call the dispatch function with an action object that tells the reducer function to either increment or decrement the count state variable.

Finally, we render the current count value and two buttons that call the event handlers when clicked. When the buttons are clicked, the dispatch function sends an action object to the reducer function, which updates the count state variable based on the action. The new count value is then re-rendered by React.

useRef

useRef is a built-in React hook that allows you to create a mutable reference to a value that persists across component re-renders. Unlike state variables or props, changes to the value of a ref do not trigger a re-render of the component.

Here’s how to use useRef:

Declare a ref variable using the useRef hook. Pass in an initial value as an argument.

const myRef = useRef(initialValue);

Use the myRef.current property to access the current value of the ref.

console.log(myRef.current);

Use the myRef.current property to update the current value of the ref.

myRef.current = newValue;

Here’s an example of how to use useRef to implement a simple input focus function:

import React, { useRef } from 'react';

function TextInput() {
// Declare a ref to the input element
const inputRef = useRef(null);

// Focus the input when the button is clicked
const handleClick = () => {
inputRef.current.focus();
};

return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}

In the above example, we declare a inputRef ref variable using the useRef hook. We then pass the inputRef variable as a ref to the input element. This allows us to access the input element's DOM node using the inputRef.current property.

We then define a handleClick function that calls the inputRef.current.focus() method, which focuses the input element when the button is clicked.

Finally, we render the input element and a button that calls the handleClick function when clicked. When the button is clicked, the handleClick function updates the current value of the inputRef ref variable, which causes the input element to be focused.

useMemo

The useMemo hook is a built-in React hook that allows you to memoize the results of a function call so that it is not re-computed unnecessarily on every re-render.

Here’s an example of how to use useMemo:

import React, { useMemo } from 'react';

function MyComponent({ prop1, prop2 }) {
// Declare a memoized value using useMemo
const memoizedValue = useMemo(() => {
// Some expensive computation based on prop1 and prop2
return prop1 + prop2;
}, [prop1, prop2]); // Dependencies list

return (
<div>
<p>Prop1: {prop1}</p>
<p>Prop2: {prop2}</p>
<p>Memoized Value: {memoizedValue}</p>
</div>
);
}

In the example above, we declare a memoizedValue variable using the useMemo hook. The first argument to useMemo is a function that returns the value to be memoized, in this case the result of some expensive computation based on prop1 and prop2. The second argument to useMemo is an array of dependencies that should trigger a re-computation of the memoized value if their value changes. In this case, we want to recompute the memoized value if either prop1 or prop2 change.

Now, every time prop1 or prop2 changes, the function provided to useMemo will be re-run, but if neither prop1 nor prop2 has changed, the memoized value will not be re-computed, and the previously memoized value will be returned instead.

The useMemo hook is useful for optimizing expensive computations, particularly those that are not related to the UI, but rather to data processing or other non-visual logic. By memoizing the result of these computations, you can reduce unnecessary re-renders and improve the performance of your React application.

useCallback

Sure! The useCallback hook is a built-in React hook that allows you to memoize a function so that it is not re-created unnecessarily on every re-render. This is particularly useful when passing down functions as props to child components, as it can prevent unnecessary re-renders of those child components.

Here’s an example of how to use useCallback:

import React, { useCallback, useState } from 'react';
import ChildComponent from './ChildComponent';

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

// Declare a memoized callback function using useCallback
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]); // Dependencies list

return (
<div>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
}

In the example above, we declare a handleClick function using the useCallback hook. The first argument to useCallback is the function to be memoized, in this case a function that updates the count state variable. The second argument to useCallback is an array of dependencies that should trigger a re-creation of the memoized function if their value changes. In this case, we want to re-create the memoized function if the count state variable changes.

Now, every time the ParentComponent re-renders, the handleClick function will not be re-created if the count state variable has not changed, and the same instance of the handleClick function will be passed down as a prop to the ChildComponent.

The useCallback hook is particularly useful for optimizing performance when passing down functions as props to child components. By memoizing these functions, you can prevent unnecessary re-renders of those child components and improve the overall performance of your React application.

useLayoutEffect

The useLayoutEffect hook is also a built-in React hook that is similar to the useEffect hook, but it runs synchronously immediately after the DOM has been updated, but before the browser has had a chance to paint those changes on the screen. This makes it a good choice for handling situations where you need to make updates to the DOM based on the latest state or prop values before those changes are painted on the screen.

Here’s an example of how to use useLayoutEffect:

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

function MyComponent() {
const [width, setWidth] = useState(window.innerWidth);

useLayoutEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};

window.addEventListener('resize', handleResize);

return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // Empty dependencies list

return (
<div>
<p>Window Width: {width}px</p>
</div>
);
}

In the example above, we use useLayoutEffect to add an event listener to the window that listens for resize events and updates the width state variable with the new inner width of the window. We also use useLayoutEffect to remove the event listener when the component is unmounted.

Because useLayoutEffect runs synchronously after the DOM has been updated, but before the browser has had a chance to paint those changes on the screen, the width state variable will always be up-to-date with the latest inner width of the window, even if that inner width changes due to a resize event.

The useLayoutEffect hook is particularly useful for situations where you need to perform DOM-related operations that depend on the latest state or prop values, such as updating the position or size of a DOM element based on changes to the state. However, because useLayoutEffect runs synchronously, it can potentially cause performance issues if it is used excessively or in situations where it is not necessary. In those cases, you may want to use the useEffect hook instead.

useImperativeHandle

The useImperativeHandle allows you to customize the instance value that is exposed to parent components when using the ref prop. This can be useful when you need to expose a specific set of functions or properties to the parent component while keeping the implementation details of the child component hidden.

Here’s an example of how to use useImperativeHandle:

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

const ChildComponent = React.forwardRef((props, ref) => {
const inputRef = useRef();

useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
},
}));

return (
<div>
<input type="text" ref={inputRef} />
</div>
);
});

function ParentComponent() {
const childRef = useRef();

const handleClick = () => {
childRef.current.focus();
};

const handleClear = () => {
childRef.current.clear();
};

return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleClick}>Focus Input</button>
<button onClick={handleClear}>Clear Input</button>
</div>
);
}

In the example above, we use useImperativeHandle to customize the instance value that is exposed to the ParentComponent when using the ref prop to reference the ChildComponent. The first argument to useImperativeHandle is the ref that is passed in from the parent component, and the second argument is a function that returns an object of functions or properties that will be exposed to the parent component.

In this case, we expose a focus function and a clear function to the ParentComponent instance. When the focus function is called, it sets the focus to the input element in the ChildComponent using the inputRef reference. When the clear function is called, it clears the value of the input element in the ChildComponent.

Now, when the ParentComponent renders, it can call the focus and clear functions on the child component instance to manipulate the input element, without needing to know the implementation details of the child component.

The useImperativeHandle hook is particularly useful for cases where you need to expose a specific set of functions or properties from a child component to its parent, while keeping the implementation details of the child component hidden. However, because it is an advanced feature, it should be used sparingly and only when necessary.

useDebugValue

The useDebugValue hook is a built-in React hook that allows you to display custom labels for custom hooks in the React Developer Tools. This can be useful when you want to give more descriptive names to custom hooks to help with debugging and inspection.

Here’s an example of how to use useDebugValue:

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

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

useDebugValue(value > 10 ? 'Big Value' : 'Small Value');

return [value, setValue];
}

function App() {
const [count, setCount] = useCustomHook(0);

return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

In the example above, we define a custom hook called useCustomHook that takes an initial value and returns an array containing the current value and a function to set the value. We use the useDebugValue hook to display a label in the React Developer Tools based on the value of the state.

In this case, if the value is greater than 10, the label displayed in the React Developer Tools will be ‘Big Value’, and if the value is less than or equal to 10, the label will be ‘Small Value’.

The useDebugValue hook takes a single argument, which is the value to display in the React Developer Tools for the custom hook. This value can be any string, number, or object that you want to use to describe the custom hook.

Note that the useDebugValue hook is purely a debugging tool and has no effect on the behavior of the custom hook or the component that uses it. It is purely used to give more descriptive names to custom hooks in the React Developer Tools for easier debugging and inspection.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Fredrick Mbugua
Fredrick Mbugua

Written by Fredrick Mbugua

Software Developer @Safaricom. Experienced in Web, Mobile (Flutter & Kotlin), CrytpoCurrency, Smart Contracts, API and Software Design, Interaction Design

No responses yet

Write a response