In the realm of React development, mastering hooks is akin to unlocking a treasure trove of efficiency and flexibility. These small, but powerful functions revolutionized how developers manage state, side effects, and more in React components. In this blog post, we'll explore some of the most important React hooks that every developer should be familiar with.
This article explores -
useState, useEffect, useContext, useRef, useMemo, useCallback, useReducer
The useState Hook
The useState hook allows you to add a state to a functional component. It takes an initial value as an argument and returns an array with two elements: the current state value and a function to update it.
Here’s an example of how to use useState to add a counter to a functional component:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment= () => {
setCount(count + 1);
}
const decrement = () => {
setCount(count - 1);
}
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>decrement</button>
</div>
);
}
In this example, we start with a count of 0 and update it every time the “Increment” and ‘’decrement’’ button is clicked. This is the most basic example of all.
The useEffect Hook
useEffect() is a React hook for performing side effects in functional components. It takes a function to run the effect and an optional array of dependencies.
The effect executes after the component renders and can return a cleanup function. If dependencies change, the effect re-runs, providing control over when it executes. Common Use Cases -
- Data Fetching
- Subscriptions
- DOM Manipulation
- State Updates
- Cleanup
useEffect(()=>{// function logic}, [dependencies])
Now if no dependency is used, and it's kept empty then the useEffect will be in effect whenever the component renders. It will effect all time. For example -
Here no matter which button is pressed the function inside useEffect executes, when button pressed component renders and the function runs.
Now when dependency added - count2,
The logic of useEffect will take effect whenever there is a change in the count2 state. So only when button 2 is clicked it executes.
Now if an empty array is added as a dependency - []
In this case, the logic of useEffect executes only once when the component renders, and when it does states are in their default value, which is 3 in the case of count1 and 0 for count2, hence logic executes once only. Keep clicking the buttons will not have any effect.
The useContext() Hook
This hook allows you to work with react context API, which is a mechanism that allows you to share or scope values to the entire component tree without passing props. Let me tell you this better with an example, let's imagine we have an object's emotions.
const emotions = {
happy: '😂',
angry: '😡',
sad: '😔'
}
To share the emotions across multiple disconnected components we can use - Context
const EmotionContext = createContext();
Now,
have this as the App.jsx -
Here's what's happening in context of the useContext hook:
- Context Creation: In context.jsx, a context is created using the createContext() function from React. This creates a context object which consists of a Provider and a Consumer.
- Provider Usage: In the Contexting component, the EmotionContext.Provider is used to provide the emotions value to its descendants. The value prop of the provider sets the value of the context, which in this case is the emotions object.
- Consumer Usage (Implicit): The descendants of the EmotionContext.Provider can consume the context value using the useContext hook or by wrapping their components with the EmotionContext.Consumer. However, in your provided code, the context value isn't directly consumed within the Contexting component.
- Usage in Descendants: Components nested within Contexting component tree can utilize the context value using the useContext hook. They can import the EmotionContext object and call useContext(EmotionContext) to access the context value.
Other Example -
The useRef() Hook
This hook allows you to create objects that will keep the same reference between renders. It can be used when you have a value that changes like setState but the difference is that it does not re-render when the value changes.
For example if we have a simple counter function -
function App() {
const count = useRef(0);
return (
<button onClick={() => count.current++}>
{count.current}
</button>
);
}
We can reference the current count, by count.current
, but when clicking on the button it does change the count in ui because useRef() doesn't trigger re-render as setState does.
But a common use case for ref is to grab native HTML elements from jsx (JavaScript XML) or DOM. So ref does not re-update the component. It returns object {current: 0}, whose value is 0 by default.
const x = useRef(0)
useEffect(()=> {
x.current = x.current + 1;
})
{x.current} //somewhere
So x is just an object with current property, and when we update that property, that is what gets persisted between our different renders. We can change this x.current as many times as we want with re-rendering. Now an example of referencing the element of a document using useRef -
import React, {useRef} from 'react';
export function App(props) {
const inputRef = useRef(null);
const handle = () => {
alert(inputRef.current.value);
}
return (
<div className='App'>
<input type="text" ref = {inputRef} />
<br/>
<button onClick={handle}>Click Me</button>
</div>
);
}
Hereafter the button click -> handle function -> inputRef which is created and used in the input element is used. All elements have ref as an attribute.
Another example -
useRef is used to create a reference to the section element. When the button is clicked, the handleClick function is called, which scrolls the page to the section using sectionRef.current.scrollIntoView({ behavior: 'smooth' }).
import React, { useRef } from 'react';
function App() {
const sectionRef = useRef(null);
const handleClick = () => {
sectionRef.current.scrollIntoView({ behavior: 'smooth' });
};
return (
<div>
<button onClick={handleClick}>Scroll to Section</button>
<section ref={sectionRef}>
{/* Content */}
</section>
</div>
);
}
useMemo() -
In React, the useMemo hook is used to memoize the result of a computation, so that the computation is only executed when its dependencies change.
This can be useful in optimizing the performance of expensive
calculations or computations in functional components.
• () => ...: This is an inline function that contains the computation or calculation you want to memoize.
• dependencyl, dependency2, ...: An array of dependencies. If any of these dependencies change, the memoized value will be recalculated.
In this example, we use useMemo to calculate the squared value of the count state. The useMemo hook will execute the inline function only when the count changes.
If the count remains the same, it will reuse the previously memoized value, thus avoiding unnecessary recalculations.
When you click the "Increment Count" button, the count will increase, and the squared count will be recalculated since the count has changed.
if you click the button multiple times without changing the count, the squared count will not be recalculated, and you won't see the console log message more than once.
Remember, useMemo is primarily used to optimize expensive computations, and using it unnecessarily can add unnecessary complexity to your code.
The useCallback() Hook
useCallback()
is a React Hook used for optimizing performance by memoizing functions. In React, whenever a component re-renders, functions defined within that component are recreated. This can be inefficient, especially when these functions are passed down to child components as props, as it can lead to unnecessary re-renders in those child components.
useCallback()
allows you to memoize a function so that it's only re-created if one of its dependencies changes. This can prevent unnecessary re-renders of child components that rely on these functions.
const memoizedCallback = useCallback(
() => {
// function body
},
[dependencies]
);
const memoizedFn = useCallback(callback,depend);
callback is the function you want to memoized, and depend is dependencies based on which callback function is re-created, when any change occurs to them.
without using useCallback, the increment function would be
recreated on every render of the ParentComponent, leading to potential performance issues.
However, by using useCallback, the memoized Increment function is only recreated when the count state changes, which optimizes performance by preventing unnecessary re-renders of the ChildComponent.
Remember that while useCallback
can help improve performance in certain situations, it's important to avoid overusing it.
Only apply it to functions that actually cause performance issues due to frequent re-creation. In most cases, React's built-in reconciliation process handles function re-creation efficiently.
The useReducer() Hook
The useReducer hook allows you to manage complex state in a functional component. It’s similar to the useState hook, but instead of a simple value, it takes a reducer function and an initial state.Think of it as a more powerful alternative to useState for complex state logic.
- State and Actions: useReducer uses a state and actions to change that state.
- Reducer Function: You create a function (reducer) that takes the current state and an action, and returns the new state.
- Initial State: You define the starting state.
- Dispatch: You get a function to send actions to the reducer to update the state.
Example -
import React, { useReducer } from 'react';
// Define the reducer function
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
export default Counter;