
React Hooks Explained
In this article, we explore React Hooks, and their use and provide sample implementations for each.
React Hooks — Summary
- 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.
- 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.
- 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.
- 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.
- useRef: The useRef hook provides a way to access the DOM node of a component or to store mutable values that persist between renders.
- useMemo: The useMemo hook allows you to memoize expensive computations so that they are only computed when necessary.
- useCallback: The useCallback hook lets you memoize functions so that they are only re-created when their dependencies change.
- 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.
- useImperativeHandle: The useImperativeHandle hook allows functional components to expose certain functions or methods to the parent component.
- 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.