What are React Hooks?
React Hooks are special functions introduced in React 16.8 that allow you to use React features like state and lifecycle methods in functional components. Before hooks, these features were only available in class components. Hooks are both in-built and custom (user defined).
When to use React Hooks?
If you’ve created a functional component in React and later need to add state or other React features that were previously only available in class components, you no longer have to convert it into a class component. With React Hooks, you can add state and other features directly to a functional component.
Some Built-in React Hooks
State Hooks
State lets a component “remember” information like user input. For example, a form component can use state to store the input value, while an image gallery component can use state to store the selected image index.
To add state to a component, use one of these Hooks:
useState
declares a state variable that you can update directly.useReducer
declares a state variable with the update logic inside a reducer function.
Context Hooks
Context lets a component receive information from distant parents without passing it as props. For example, your app’s top-level component can pass the current UI theme to all components below, no matter how deep.
useContext
reads and subscribes to a context.
Ref Hooks
Ref hooks allow you to create a reference to a DOM element or store mutable values that persist across renders. Unlike with state, updating a ref does not re-render your component.
useRef
declares a ref. You can hold any value in it, but most often it’s used to hold a DOM node.useImperativeHandle
lets you customize the ref exposed by your component. This is rarely used.
Effect Hooks
Effect hooks let you perform side effects in functional components. Side effects include tasks like fetching data from an API, interacting with the DOM (e.g., updating the page title), setting up subscriptions or timers.
useEffect
connects a component to an external system.useLayoutEffect
fires before the browser repaints the screen. You can measure layout here.useInsertionEffect
fires before React makes changes to the DOM. Libraries can insert dynamic CSS here.
Note : Before hooks, side effects were handled using lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount in class components.
Performance Hooks
A common way to optimize re-rendering performance is to skip unnecessary work. For example, you can tell React to reuse a cached calculation or to skip a re-render if the data has not changed since the previous render. To skip calculations and unnecessary re-rendering, use one of these Hooks:
useMemo
lets you cache the result of an expensive calculation.useCallback
lets you cache a function definition before passing it down to an optimized component.
Sometimes, your app has to do heavy tasks, like showing search results for a big dataset or rendering complex charts. These tasks can slow down things like typing in a search bar, making your app feel laggy.
useTransition
allows React to pause the heavy work and focus on the important stuff (like updating the search bar as you type).useDeferredValue
Lets you delay updating the slow parts (like search results) so that quick actions (like typing) don’t get blocked.
Some Other Hooks
These Hooks are mostly useful to library authors and aren’t commonly used in the application code.
useDebugValue
lets you customize the label React DevTools displays for your custom Hook.useId
lets a component associate a unique ID with itself. Typically used with accessibility APIs.useSyncExternalStore
lets a component subscribe to an external store.
React Hooks in Detail
Let’s now study about all the hooks provided by React.
Note : All the hooks can only be called at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.
useState
The useState
hook in React is used to add state to functional components. It allows you to create variables that React watches for changes. When the state changes, the component automatically re-renders to reflect those changes.
const [state, setState] = useState(initialState);
How does it works ?
1. Initialize State: You provide an initial value for the state. Can be a static value or a function for lazy initialization.
Static: useState(0)
Lazy: useState(() => calculateInitialValue()) (runs only once).
2. Get State and Updater Function: useState returns two things:
The current state value : During the first render, it will match the
initialState
you have passed.A set function to update the state : It lets you update the state to a different value and trigger a re-render. It accepts a parameter which indicates the new value of the state.
When to use it ?
For simple, local state management (e.g., counters, form inputs).
When the component’s state doesn’t need to be shared globally.
Caveats
The
setState
function only updates the state variable for the next render. If you read the state variable after calling thesetState
function, you will still get the old value that was on the screen before your call.If the new value you provide is identical to the current
state
, React will skip re-rendering the component and its children**.** This is an optimization. Although in some cases React may still need to call your component before skipping the children, it shouldn’t affect your code.React batches state updates. It updates the screen after all the event handlers have run and have called their
setState
functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can useflushSync
.
useEffect
The
useEffect
hook handles the effects of the dependency array. TheuseEffect
Hook allows us to perform side effects on the components. fetching data, directly updating the DOM and timers are some side effects. It is called every time any state if the dependency array is modified or updated.useEffect(()=>{ //callback function }, [dependency array]);
Note : useEffect returns undefined.
How does it works ?
The useEffect hook takes two arguments:
A callback function: Contains the side effect code. Your setup function may also optionally return a cleanup function. When your component is added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. After your component is removed from the DOM, React will run your cleanup function.
A dependency array (optional): Tells React when to re-run the effect. The callback function will be again executed if there are any changes to the states provided in the dependency array.
When does it work ?
Without a dependency array: Runs after every render (both initial render and updates).
With an empty dependency array []: Runs only after the initial render.
With specific dependencies [dep1, dep2]: Runs only when the listed dependencies change.
Caveats
Effects only run on the client. They don’t run during server rendering.
If your effect involves resources that need to be cleaned up (e.g., timers, subscriptions, event listeners), you can return a cleanup function from useEffect.
useContext
useContext
is a React hook that allows you to consume context within a functional component. It provides a way to share values (like state or functions) across your component tree without having to pass props down manually at every level.
How does it works ?
useContext
is used with Context objects. Context helps you avoid prop drilling, where props are passed down through many layers of components just to reach a deeply nested component.
Creating a Context
Before using useContext, you need to create a context using createContext()
and provide it in a parent component using the Context.Provider
. It can take an argument as initialValue.
const ThemeContext = createContext(); //Create context
const App = () => {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<ChildComponent />
</ThemeContext.Provider>
);
}
ThemeContext.Provider wraps the components that need access to the context and provides a value to be shared.
Consuming a Context
To access the context value in any child component, use the useContext
hook.
function ChildComponent() {
const { theme, setTheme } = useContext(ThemeContext); // Access Context value
return (
<div>
<p>Current theme: {theme}</p> <!-- Using theme from Context -->
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Toggle Theme
</button>
</div>
);
}
When to use it ?
Global State Management: To avoid prop drilling,
useContext
helps manage values like authentication status, theme, user settings, or language preferences at the top level and easily access them across components.Shared Data: For scenarios where multiple components need access to the same data, like a cart in an e-commerce app, you can provide that data at a high level and consume it in various child components.
Note : useContext returns the context value for the calling component. It is determined as the value passed to the closest SomeContext.Provider above the calling component in the tree. If there is no such provider, then the returned value will be the defaultValue you have passed to createContext for that context. The returned value is always up-to-date. React automatically re-renders components that read some context if it changes.
Caveats
useContext()
call in a component is not affected by providers returned from the same component. The corresponding<Context.Provider>
needs to be above the component doing theuseContext()
call.React automatically re-renders all the children that use a particular context starting from the provider that receives a different
value
.useContext
is only used in functional components. Class components would use theContext.Consumer
component instead.
useActionState
useActionState
is a Hook that allows you to update state based on the result of a form action. It is used to simplify the management of form submissions and asynchronous actions. It works by allowing you to track the state of an action—whether it’s pending, successful, or encountered an error—while also managing the form data.
const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);
It receives three parameters :
fn
: The function to be called when the form is submitted or button pressed. When the function is called, it will receive the previous state of the form (initially theinitialState
that you pass, subsequently its previous return value) as its initial argument, followed by the arguments that a form action normally receives.initialState
: The value you want the state to be initially. It can be any serializable value. This argument is ignored after the action is first invoked.permalink
(optional) : A string containing the unique page URL that this form modifies. For use on pages with dynamic content (eg: feeds) in conjunction with progressive enhancement: iffn
is a server function and the form is submitted before the JavaScript bundle loads, the browser will navigate to the specified permalink URL, rather than the current page’s URL. Ensure that the same form component is rendered on the destination page (including the same actionfn
andpermalink
) so that React knows how to pass the state through. Once the form has been hydrated, this parameter has no effect.
Note: useActionState returns an array with the following values:
The current state : During the first render, it will match the initialState you have passed. After the action is invoked, it will match the value returned by the action.
A new action that you can pass as the action prop to your form component or formAction prop to any button component within the form.
The isPending flag that tells you whether there is a pending Transition.
useCallback
useCallback
is a React Hook that lets you cache a function definition between re-renders. It helps optimize performance by preventing unnecessary re-creations of functions on every render, which can be particularly useful in cases where functions are passed as props to child components, triggering unnecessary re-renders. It ensures that a function is not redefined unless certain dependencies change.
const memoizedCallback = useCallback(
() => { /* function body */ },
[dependency1, dependency2] // List of dependencies that will trigger re-creation
);
useDeferredValue
useDeferredValue
improves the performance of updates in situations where part of the UI can be deferred. It allows React to delay the update of certain state values, prioritizing more critical updates (like user input) and deferring less important updates (like rendering large data sets or non-critical UI changes). This helps create a smoother experience, especially during heavy computation or rendering tasks.
const deferredValue = useDeferredValue(value);
The useDeferredValue
hook takes a state value and returns a deferred version of that value. When the value changes, React will only update the deferred value in the background, ensuring that the user interface remains responsive by allowing more critical updates to render first.
Parameters
value
: The value you want to defer. It can have any type.initialValue
(optional) : A value to use during the initial render of a component. If this option is omitted,useDeferredValue
will not defer during the initial render, because there’s no previous version ofvalue
that it can render instead.
Note: During the initial render, the returned deferred value will be the initialValue, or the same as the value you provided. During updates, React will first attempt a re-render with the old value (so it will return the old value), and then try another re-render in the background with the new value (so it will return the updated value).
Caveats
useDeferredValue
does not by itself prevent extra network requests.When an update is inside a Transition,
useDeferredValue
always returns the newvalue
and does not spawn a deferred render, since the update is already deferred.The values you pass to
useDeferredValue
should either be primitive values (like strings and numbers) or objects created outside of rendering. If you create a new object during rendering and immediately pass it touseDeferredValue
, it will be different on every render, causing unnecessary background re-renders.The background re-render caused by
useDeferredValue
does not fire Effects until it’s committed to the screen. If the background re-render suspends, its Effects will run after the data loads and the UI updates.
useId
The useId
hook helps in generating unique IDs for components. It is particularly useful when you need unique identifiers for elements like form controls or ARIA attributes that need to be associated with labels and other elements.
const id = useId();
Note : useId returns a unique ID string associated with this particular useId call in this particular component.
It generates stable and unique IDs that persist across renders and ensure that components get unique identifiers without the need for manually managing them. useId
should not be used to generate keys in a list.
useMemo
useMemo
is a React hook that helps optimize performance by memoizing (remembering) the result of a computation so that it doesn’t have to be recalculated on every render. This can be particularly useful for expensive computations or large data sets.
const cachedValue = useMemo(calculateValue, dependencies)
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
It ensures that a value is only recomputed when its dependencies change, preventing unnecessary recalculations.
useOptimistic
The useOptimistic
hook in React is a way to implement an “optimistic UI,” where the UI is updated immediately after an action is initiated, before the server’s response is received. This pattern provides users with immediate feedback, making the app feel faster and more responsive.
const [optimisticState, addOptimistic] = useOptimistic(currentState, updateFn);
How does it works ?
The hook works by allowing the immediate update of the state with a “temporary” optimistic version of the data. Then, once the server responds, you can update the state again with the real response. If an error occurs, the UI can be reverted to its previous state to reflect the failure.
Parameters
currentState
: The current state of the application.updateFn(currentState, optimisticValue)
: a function that takes the current state and the optimistic value passed toaddOptimistic
and returns the resulting optimistic state. It must be a pure function.updateFn
takes in two parameters. ThecurrentState
and theoptimisticValue
. The return value will be the merged value of thecurrentState
andoptimisticValue
.
Note : It returns the optimistic state and a function to change the optimistic state.
Here is an example of a shopping cart
import { useState } from 'react';
import { useOptimistic } from 'react';
const [cartItems, setCartItems] = useState([]);
const [optimisticState, addOptimistic] = useOptimistic(cartItems, (currentItems, newItem) => [...currentItems, newItem]);
const addItemToCart = async (item) => {
// Optimistic update
addOptimistic(item); // Add item to UI immediately
try {
const updatedCart = await addItemToServer(item); // Simulate server call
setCartItems(updatedCart); // If successful, update with server response
} catch (error) {
console.error("Failed to add item", error);
// Rollback by restoring the previous state
setCartItems(cartItems); // Revert the optimistic state to previous state
}
};
useReducer
useReducer
is used to manage more complex state logic compared touseState
. It provides an alternative touseState
when the state transitions involve multiple actions or are difficult to handle with simple state updates.useReducer
uses a reducer function, which takes the current state and an action, then returns a new state based on the action’s type and payload. This is similar to how reducers work in Redux but is built directly into React.
const [state, dispatch] = useReducer(reducer, initialState);
reducer: A function that determines how the state changes based on an action.
initialState: The initial state value.
state: The current state.
dispatch: A function to send actions to the reducer.
Example of a 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('Unknown action type');
}
}
Caveats
The
dispatch
function only updates the state variable for the next render. If you read the state variable after calling thedispatch
function, you will still get the old value that was on the screen before your call.If the new value you provide is identical to the current
state
, React will skip re-rendering the component and its children. This is an optimization. React may still need to call your component before ignoring the result, but it shouldn’t affect your code.
useRef
useRef
is a React Hook that provides a way to interact with and persist values across renders without triggering a re-render. It is often used to directly interact with DOM elements, store mutable values, or maintain a reference to a value that doesn’t change frequently.
const ref = useRef(initialValue);
Note : useRef returns an object with a single property → current : Initially, it’s set to the initialValue you have passed. You can later set it to something else. If you pass the ref object to React as a ref attribute to a JSX node, React will set its current property.
Caveats
You can mutate the
ref.current
property. Unlike state, it is mutable. However, if it holds an object that is used for rendering (for example, a piece of your state), then you shouldn’t mutate that object.When you change the
ref.current
property, React does not re-render your component. React is not aware of when you change it because a ref is a plain JavaScript object.Do not write or read
ref.current
during rendering, except for initialization. This makes your component’s behavior unpredictable.
useTransition
The
useTransition
hook in React is used to manage non-blocking state updates for smoother user interfaces. It helps prioritize updates, allowing essential operations (like typing in an input) to remain responsive while deferring less critical updates (like rendering a chart or a list). This improves the user experience in scenarios where some updates are computationally expensive or take time to complete.
const [isPending, startTransition] = useTransition();
Note : It returns an array with exactly two items :
The isPending flag that tells you whether there is a pending Transition.
startTransition: A function to wrap non-urgent updates in.
Caveats
useTransition
is a Hook, so it can only be called inside components or custom Hooks. If you need to start a Transition somewhere else (for example, from a data library), call the standalonestartTransition
instead.You can wrap an update into a Transition only if you have access to the
set
function of that state. If you want to start a Transition in response to some prop or a custom Hook value, tryuseDeferredValue
instead.The function you pass to
startTransition
is called immediately, marking all state updates that happen while it executes as Transitions. If you try to perform state updates in asetTimeout
, for example, they won’t be marked as Transitions.The
startTransition
function has a stable identity, so you will often see it omitted from Effect dependencies, but including it will not cause the Effect to fire. If the linter lets you omit a dependency without errors, it is safe to do.Transition updates can’t be used to control text inputs.
useFormStatus
Note : It is a React DOM Hook. The react-dom package contains Hooks that are only supported for web applications (which run in the browser DOM environment). These Hooks are not supported in non-browser environments like iOS, Android, or Windows applications
useFormStatus
is a Hook that gives you status information of the last form submission.
const { pending, data, method, action } = useFormStatus();
How does it works ?
This hook must be used inside a component rendered within a <form>. It provides an object with properties like:
• pending
: A boolean. If true
, this means the parent <form>
is pending submission. Otherwise, false
.
• data
: An object implementing the FormData interface
that contains the data the parent <form>
is submitting. If there is no active submission or no parent <form>
, it will be null
.
• method
: A string value of either 'get'
or 'post'
. This represents whether the parent <form>
is submitting with either a GET
or POST
HTTP method. By default, a <form>
will use the GET
method and can be specified by the method
property.
• action
: A reference to the function passed to the action
prop on the parent <form>
. If there is no parent <form>
, the property is null
. If there is a URI value provided to the action
prop, or no action
prop specified, status.action
will be null
.
Caveats
The
useFormStatus
Hook must be called from a component that is rendered inside a<form>
.useFormStatus
will only return status information for a parent<form>
. It will not return status information for any<form>
rendered in that same component or children components.
Custom Hooks
A custom hook in React is a JavaScript function that allows you to reuse logic across multiple components. React hooks like useState
, useEffect
, and useContext
are commonly used within custom hooks to encapsulate reusable logic that doesn’t directly involve rendering UI but can be used by multiple components.
Custom hooks offer a way to share functionality without repeating code, keeping components clean and modular.
Why to use them ?
Reusability : Custom hooks allow you to extract logic from components and reuse it in different places, promoting DRY (Don’t Repeat Yourself) code.
Separation of Concerns: By isolating non-UI logic in custom hooks, components focus only on rendering the UI.
Encapsulation: Custom hooks enable the encapsulation of related logic, making it easier to manage and test.
Simplifies Complex Logic: Custom hooks allow complex logic, such as handling form inputs or managing APIs, to be shared across components without redundancy.
Easier Testing: Since custom hooks are functions, they can be independently tested, making it easier to ensure that your logic works as expected.
Better Code Organization: You can group related logic into a custom hook, making your components more readable and maintainable.
Creating a Custom Hook
Let’s create a custom hook for a restaurant website which fetches the menu of any particular restaurant from a API and using the restaurant ID.
const useRestaurantMenu = (resId) => {
const [resInfo, setResInfo] = useState(null);
useEffect(()=>{
fetchData();
}, [])
const fetchData = async () => {
const data = await fetch(API + resId);
const json = await data.json();
setResInfo(json);
};
return resInfo;
}
export default useRestaurantMenu;
Now we can use our custom hook useRestaurantMenu
in other components to fetch the menu of restaurants by importing it.
Let’s create one more custom hook to check if the user is online or offline.
const useOnlineStatus = () => {
const [onlineStatus, setOnlineStatus] = useState(false);
useEffect(() => {
window.addEventListener("offline", () => {
setOnlineStatus(false);
});
window.addEventListener("online", () => {
setOnlineStatus(true);
});
}, []);
return onlineStatus;
}
export default useOnlineStatus;
Caveats
Custom hooks must follow React’s rules of hooks, meaning they can only be called at the top level of a component or another hook.
Custom hooks can return any value, object, array, or function that the component needs.
Make sure that logic inside custom hooks doesn’t lead to side effects unless it’s part of React’s lifecycle, such as in useEffect.
The name of the Custom Hook should start with the prefix
use
(Recommended but not mandatory).