9 React Hooks you should know
React Hooks revolutionized the way we write React components by introducing a more intuitive and efficient approach to managing state, context, and side effects. In this article, I will explore the different categories of hooks and dive into each one, discussing their purpose, usage, and benefits.
State Hooks
useState
The useState hook allows functional components to manage the local state. By calling this hook, you can declare variables that retain their values between renderings. It returns a stateful value and a function to update it, enabling you to handle component-level states easily.
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
return (
<div>
<h2>Counter: {count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
export default Counter;
In the above example, we start by importing the useState hook from the ‘react’ library. We then define a functional component called Counter. Inside this component, we call the useState hook to declare a state variable called count and its corresponding update function, setCount. The initial value of count is set to 0.
We define two additional functions, increment and decrement, which are triggered by the click events of the respective buttons. When the increment function is called, it updates the count state by calling setCount with the current count value incremented by 1. Similarly, the decrement function reduces the count state by 1.
Whenever the state is updated, React will re-render the component, reflecting the new value of the count in the UI.
useReducer
The useReducer hook is an alternative to useState and provides a more powerful way to manage complex state logic. It takes a reducer function and an initial state, returning the current state and a dispatch function. This hook is particularly useful when state updates depend on the previous state or involve multiple actions.
import React, { useReducer } from 'react';
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error('Unsupported action type');
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const increment = () => {
dispatch({ type: 'increment' });
};
const decrement = () => {
dispatch({ type: 'decrement' });
};
const reset = () => {
dispatch({ type: 'reset' });
};
return (
<div>
<h2>Counter: {state.count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
};
export default Counter;
In this example, we define a functional component called Counter. We start by declaring an initialState object, which contains the initial value for the count property.
Next, we define a reducer function that takes the current state and an action as arguments. The reducer handles different action types using a switch statement. In this case, we have ‘increment’, ‘decrement’, and ‘reset’ actions. For each action, the reducer returns a new state object based on the current state and the action type.
Inside the Counter component, we call the useReducer hook and pass in the reducer function and the initialState. The useReducer hook returns an array with two elements: the current state and a dispatch function.
We define three functions, increment, decrement, and reset, which are called when the respective buttons are clicked. These functions dispatch the corresponding actions to the reducer using the dispatch function.
When an action is dispatched, the reducer is called with the current state and the action, and it returns the updated state. React then re-renders the component, reflecting the new state value in the UI.
Context Hooks
useContext
The useContext hook enables components to access values from a context provider without using nested consumers. By providing a context object, it returns the current context value. This hook simplifies the process of sharing data or functionality across multiple components in a React tree.
First, let’s create a context file called ThemeContext.js:
import React from 'react';
const ThemeContext = React.createContext();
export default ThemeContext;
In this file, we use the createContext function from React to create a new context called ThemeContext.
Next, let’s create a component called App.js that wraps the application with the ThemeContext.Provider:
import React from 'react';
import ThemeContext from './ThemeContext';
import Header from './Header';
import Content from './Content';
const App = () => {
const theme = 'light';
return (
<ThemeContext.Provider value={theme}>
<div>
<Header />
<Content />
</div>
</ThemeContext.Provider>
);
};
export default App;
In this example, we set the value of the theme variable to ‘light’. We wrap the entire application within the ThemeContext.Provider component and pass the theme value as the value prop.
Now, let’s create two components, Header.js and Content.js, which will access the shared theme value using the useContext hook:
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
const Header = () => {
const theme = useContext(ThemeContext);
return (
<header style={{ background: theme === 'light' ? 'lightblue' : 'darkblue', padding: '1rem' }}>
<h1>Header</h1>
</header>
);
};
export default Header;
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
const Content = () => {
const theme = useContext(ThemeContext);
return (
<div style={{ background: theme === 'light' ? 'lightgray' : 'darkgray', padding: '1rem' }}>
<h2>Content</h2>
<p>This is the content area.</p>
</div>
);
};
export default Content;
In both Header.js and Content.js, we import the useContext hook from React and the ThemeContext we created earlier. We then use the useContext hook to access the theme value from the shared context.
Inside the components, we use the theme value to conditionally apply styles based on the theme. In the Header component, we set the background color of the header based on the theme value. Similarly, in the Content component, we set the background color of the content area based on the theme value.
With the useContext hook, the components are able to directly access the theme value from the shared context without the need for nested consumers.
Ref Hooks
useRef
The useRef hook provides a way to create a mutable reference that persists across renderings. It returns a mutable ref object that can hold any value. This hook is commonly used for accessing DOM elements, managing focus, or persisting values between renders without triggering a re-render.
import React, { useRef } from 'react';
const InputForm = () => {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted value:', inputRef.current.value);
inputRef.current.value = '';
};
return (
<form onSubmit={handleSubmit}>
<label>
Enter a value:
<input type="text" ref={inputRef} />
</label>
<button type="submit">Submit</button>
</form>
);
};
export default InputForm;
In this example, we have a component called InputForm that contains a form with an input field and a submit button. We use the useRef hook to create a mutable reference called inputRef.
Inside the component, we define a handleSubmit function that is called when the form is submitted. The function prevents the default form submission behavior and logs the current value of the input field using inputRef.current.value.
By using the ref attribute and passing inputRef to the input field, we establish a connection between the input field and the inputRef reference. This allows us to access and manipulate the input field directly using the inputRef.
The useRef hook allows us to reference DOM elements or other values and persist them across re-renders without causing a re-render when the reference is updated. It’s commonly used for accessing DOM elements, managing focus, or persisting values between renders without triggering re-renders.
useImperativeHandle
The useImperativeHandle hook allows you to customize the instance value that is exposed to parent components when using the ref prop. It is useful when you want to expose specific methods or properties from a child component’s imperative handle to its parent component.
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const Input = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
getValue: () => {
return inputRef.current.value;
}
}));
return (
<input type="text" ref={inputRef} />
);
});
const ParentComponent = () => {
const inputRef = useRef();
const handleButtonClick = () => {
inputRef.current.focus();
console.log('Input value:', inputRef.current.getValue());
};
return (
<div>
<Input ref={inputRef} />
<button onClick={handleButtonClick}>Focus Input</button>
</div>
);
};
export default ParentComponent;
In this example, we have a child component called Input and a parent component called ParentComponent. The Input component utilizes the forwardRef function from React to allow the parent component to access its internal functionality.
Inside the Input component, we create a mutable reference called inputRef using the useRef hook. Then, we use the useImperativeHandle hook to specify the exposed functions and values that the parent component can access through the ref.
Recommended by LinkedIn
In this case, we define two functions: focus and getValue. The focus function uses inputRef.current.focus() to programmatically focus the input element when called. The getValue function retrieves the current value of the input element using inputRef.current.value.
By using useImperativeHandle, we can carefully select and expose specific functions or values from the child component to the parent component, controlling what the parent component can access.
In the ParentComponent, we create a ref using the useRef hook called inputRef. We pass this ref to the Input component using the ref attribute.
Inside the ParentComponent, we define a handleButtonClick function that is called when the button is clicked. This function demonstrates how we can interact with the child component’s exposed functions. We call inputRef.current.focus() to programmatically focus the input element and use inputRef.current.getValue() to retrieve the current value of the input.
The useImperativeHandle hook allows us to control the child component’s imperative behavior and provide a more controlled interface for the parent component to interact with.
Effect Hooks
useEffect
The useEffect hook allows you to perform side effects in functional components. It replaces the lifecycle methods of class components, such as componentDidMount, componentDidUpdate, and componentWillUnmount. By providing a callback function, you can manage asynchronous operations, handle subscriptions, or update the DOM based on component updates.
import React, { useState, useEffect } from 'react';
const ExampleComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Component mounted');
return () => {
console.log('Component unmounted');
};
}, []);
useEffect(() => {
console.log(`Count updated: ${count}`);
}, [count]);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>Increment</button>
</div>
);
};
export default ExampleComponent;
In this example, we have a component called ExampleComponent that displays a count and a button to increment the count.
The first useEffect hook is used for component mounting and unmounting. It is called once when the component is mounted, and the function inside the hook is executed. In this case, it logs ‘Component mounted’ to the console. Additionally, the hook can return a cleanup function, which is executed when the component is unmounted. In this example, the cleanup function logs ‘Component unmounted’ to the console.
The second useEffect hook is used to observe changes in the count state variable. It is called whenever the count value changes. In this case, it logs the updated value of count to the console.
By providing an empty array ([]) as the second argument to the first useEffect hook, we ensure that it is only executed once during component mounting and unmounting.
By providing [count] as the second argument to the second useEffect hook, we specify that it should only be executed when the count value changes.
Whenever the component is mounted, unmounted, or the count value changes, the respective useEffect hooks are executed, performing the defined side effects.
The useEffect hook allows us to handle side effects, such as data fetching, subscriptions, or DOM manipulation, in a declarative way within functional components.
useLayoutEffect
The useLayoutEffect hook is similar to useEffect, but it fires synchronously after all DOM mutations. It’s useful when you need to perform measurements or access the DOM immediately after a component’s render. This hook ensures that the effect runs before the browser has a chance to paint, preventing any visible flickering.
import React, { useState, useEffect, useLayoutEffect } from 'react';
const ExampleComponent = () => {
const [width, setWidth] = useState(0);
useEffect(() => {
console.log('useEffect: Component rendered');
});
useLayoutEffect(() => {
console.log('useLayoutEffect: Component rendered');
setWidth(document.documentElement.clientWidth);
});
return (
<div>
<h2>Width: {width}px</h2>
</div>
);
};
export default ExampleComponent;
In this example, we have a component called ExampleComponent that displays the width of the document in pixels.
Inside the component, we use the useState hook to declare a state variable called width and its corresponding update function, setWidth. The initial value of width is set to 0.
We then use the useEffect and useLayoutEffect hooks to perform side effects in the component.
The useEffect hook is used to log a message to the console whenever the component is rendered. It runs after the component is rendered and does not block the painting of the screen.
The useLayoutEffect hook is used to log a message to the console and set the width state variable to the current width of the document. It runs synchronously after the DOM is updated but before the browser paints the screen. This allows us to perform measurements or manipulations that may affect the visual layout of the component.
In this example, when the component renders, both useEffect and useLayoutEffect are called. However, useEffect is called after the component is rendered and does not impact the visual layout. On the other hand, useLayoutEffect is called before the browser paints the screen, allowing us to access the current width of the document and update the width state accordingly.
The useLayoutEffect hook is typically used when we need to perform imperative DOM manipulations or measurements that depend on the visual layout of the component. It runs synchronously and is generally avoided in most cases, as the use of useEffect is sufficient for handling most side effects.
Performance Hooks
useMemo
The useMemo hook enables memoization of expensive computations within functional components. By caching the result of a computation, it prevents unnecessary re-calculations when the component re-renders. This hook is especially useful when dealing with computationally intensive tasks or expensive data transformations.
import React, { useState, useMemo } from 'react';
const ExampleComponent = () => {
const [number, setNumber] = useState(0);
const expensiveCalculation = useMemo(() => {
console.log('Performing expensive calculation...');
let result = 0;
for (let i = 0; i < number * 1000000; i++) {
result += i;
}
return result;
}, [number]);
const increment = () => {
setNumber(number + 1);
};
return (
<div>
<h2>Number: {number}</h2>
<h2>Expensive Calculation: {expensiveCalculation}</h2>
<button onClick={increment}>Increment</button>
</div>
);
};
export default ExampleComponent;
In this example, we have a component called ExampleComponent that displays a number and performs an expensive calculation based on that number.
We use the useMemo hook to perform the expensive calculation. The expensive calculation is only executed when the number value changes.
The callback function inside useMemo performs the expensive calculation. In this example, it simply iterates a loop number * 1000000 times and adds the value of i to a result variable. The result of the calculation is returned.
By passing [number] as the dependency array to useMemo, we ensure that the expensive calculation is re-evaluated whenever the number value changes. If the value of number remains the same, the previous result of the calculation is returned without re-computing it.
By using useMemo, we can optimize the performance of our component by avoiding unnecessary re-computations of expensive calculations when the dependencies haven’t changed. It allows us to memoize values and reuse them across renders, reducing unnecessary computations and improving overall performance.
useCallback
The useCallback hook is similar to useMemo, but it memoizes callback functions instead of values. It’s particularly useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary re-renders.
import React, { useState, useCallback } from 'react';
const UseCallbackComponent = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>Increment</button>
</div>
);
};
export default UseCallbackComponent;
In this example, we have a component called UseCallbackComponent that displays a count and a button to increment the count.
We then use the useCallback hook to memoize the increment function. The useCallback hook returns a memoized version of the callback function that only changes if its dependencies change.
In this case, the increment function is wrapped in useCallback without any dependencies specified, resulting in a memoized version of the increment function that doesn’t change across re-renders.
By using useCallback to memoize the increment function, we ensure that the function reference remains the same across re-renders as long as its dependencies remain unchanged. This can be beneficial in scenarios where the function is passed down to child components as a prop, preventing unnecessary re-rendering of those components if the function reference hasn’t changed.
Conclusion
React Hooks have significantly simplified state management, context sharing, side effect handling, and performance optimization in React applications. By utilizing the different categories of hooks, such as state hooks, context hooks, ref hooks, effect hooks, and performance hooks, developers can write more concise, readable, and efficient code. Understanding the purpose and usage of these hooks empowers developers to leverage the full potential of React in building robust and scalable applications. If you need more details you can visit the official document of react from here