React Hooks, introduced in React 16.8, revolutionized how developers build functional components by allowing them to use state and other features of React without writing a class. With hooks, developers can reuse logic across components, manage component lifecycle, and work more efficiently within functional components.
In this article, we’ll dive into the basics of React Hooks, explore the most common hooks, and understand how they enhance the React ecosystem.
What Are React Hooks?
Before React Hooks, managing state and lifecycle methods within React required the use of class components. Hooks allow functional components to handle side effects, state, and other React features, simplifying code and reducing boilerplate. Hooks follow these rules:
- Only call hooks at the top level: Hooks should be used at the top of a functional component or in other hooks, not inside loops, conditions, or nested functions.
- Only call hooks from React components: Hooks must be called inside a React functional component or custom hook.
Commonly Used React Hooks
Get ready to have a close look at the most commonly used hooks and their use cases.
1. useState
The useState is the most widely used React hook which helps you to add state to functional components. It accepts an initial state and returns a pair: the current state value and a function to update that state.
Example:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} Times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}In this example, useState initializes count to 0 and the setCount function updates the state when the button is clicked.
2. useEffect
The useEffect hook is primarily used to perform side effects in function components. Side effects could be data fetching, manual DOM manipulation, subscriptions, or logging.
Example:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} Times`;
}, [count]); // Only re-run the effect if count changes
return (
<div>
<p>You clicked {count} Times</p>
<button onClick={() => setCount(count + 1)}>
Click Me
</button>
</div>
);
}In this example, the useEffect hook updates the document title every time the count changes. The second argument, [count], ensures that the effect only runs when count is updated, optimizing performance by avoiding unnecessary re-renders.
3. useContext
useContext allows you to subscribe to React context without writing a class component. It enables components to access shared data like themes, user settings, or authentication status without prop drilling.
Example:
import React, { useContext } from 'react';
const ThemeContext = React.createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button style={{ background: theme === 'dark' ? '#333' : '#FFF' }}>I am styled by theme context!</button>;
}Here, useContext accesses the value from ThemeContext, making the ThemedButton responsive to changes in theme without passing props through intermediary components.
4. useReducer
useReducer is a React hook which allows you to add a reducer to your component. It is similar to useState. But is more suitable for managing complex state logic involving multiple sub-values or when the next state depends on the previous one. It also works well when managing local component state and state transitions in a predictable way.
Example:
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}In this example, useReducer helps manage state transitions by dispatching actions (increment and decrement). It is commonly used for more complex state updates than what useState can handle. In continuation to what are React hooks, we discuss useRef next.
5. useRef
useRef creates a mutable object whose .current property persists through renders. It is often used for accessing DOM elements directly or storing mutable values that don’t trigger a re-render when updated.
Example:
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// Access the input element and focus it
inputEl.current.focus();
};
return (
<div>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</div>
);
}In this example, useRef is used to access the DOM input element directly and focus it when the button is clicked.
6. useMemo
useMemo memoizes expensive computations to optimize performance, ensuring that the calculation only re-runs when its dependencies change.
If you are interested in the practical application of the React hooks in real world, please visit here. We offer affordable web design and development using React and similar technolgoies.
Example:
import React, { useState, useMemo } from 'react';
function ExpensiveCalculationComponent({ a, b }) {
const memoizedValue = useMemo(() => {
return a + b; // Imagine a complex calculation here
}, [a, b]);
return <div>The result is: {memoizedValue}</div>;
}In this example, useMemo ensures that the calculation only happens when a or b changes, preventing unnecessary re-calculations on every render.
7. useCallback
The useCallback React hook memoizes functions, and then returns a memoized version that only changes if one of the dependencies has changed. It is used when you have a component in which the child is rerendering again and again without a need. This can optimize performance, especially when passing functions to child components.
import React, { useState, useCallback } from 'react';
const ExpensiveComponent = React.memo(({ onClick }) => {
console.log("Rendered");
return <button onClick={onClick}>Click Me</button>;
});
function App() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((c) => c + 1);
}, []); // Memoized function that only changes when dependencies change
return (
<div>
<p>{count}</p>
<ExpensiveComponent onClick={increment} />
</div>
);
}8. useLayoutEffect
useLayoutEffect is a version of useEffect. It fires synchronously after all DOM mutations. It’s useful when you need to perform side effects that involve DOM measurements or manipulations that should happen before the browser paints.
import React, { useLayoutEffect, useRef } from 'react';
function Box() {
const boxRef = useRef();
useLayoutEffect(() => {
console.log(boxRef.current.getBoundingClientRect());
}, []);
return <div ref={boxRef} style={{ width: '200px', height: '200px', backgroundColor: 'blue' }} />;
}9. useImperativeHandle
useImperativeHandle is a React hook that customizes the instance value that is exposed when using ref. It’s useful when you want to expose certain actions of a component without giving full access to the DOM.
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus()
}));
return <input ref={inputRef} {...props} />;
});
function App() {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</div>
);
}10. useDebugValue
useDebugValue is useful for custom hooks. It allows you to display a label in React DevTools for easier debugging.
import React, { useState, useDebugValue } from 'react';
function useCustomHook(value) {
const [state, setState] = useState(value);
useDebugValue(state ? 'On' : 'Off'); // Debug label
return [state, setState];
}
function App() {
const [isOn, toggle] = useCustomHook(false);
return (
<div>
<p>{isOn ? 'On' : 'Off'}</p>
<button onClick={() => toggle(!isOn)}>Toggle</button>
</div>
);
}11. useTransition (React 18)
useTransition allows you to mark some updates as “non-urgent,” letting React update the UI without blocking high-priority updates (such as typing input).
import React, { useState, useTransition } from 'react';
function App() {
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setInput(e.target.value);
startTransition(() => {
setList([...Array(10000).keys()].map((item) => e.target.value));
});
};
return (
<div>
<input type="text" value={input} onChange={handleChange} />
{isPending ? <p>Loading...</p> : <ul>{list.map((item, i) => <li key={i}>{item}</li>)}</ul>}
</div>
);
}12. useDeferredValue (React 18)
useDeferredValue allows you to defer a value, making React wait to update lower-priority renders until high-priority updates finish. This is useful for complex UI that may cause jank.
import React, { useState, useDeferredValue } from 'react';
function App() {
const [input, setInput] = useState('');
const deferredInput = useDeferredValue(input);
return (
<div>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
<p>Deferred Value: {deferredInput}</p>
</div>
);
}13. useId (React 18)
useId generates unique IDs that are stable across server and client renders, useful for accessibility and form inputs.
import React, { useId } from 'react';
function App() {
const id = useId();
return (
<div>
<label htmlFor={id}>Name: </label>
<input id={id} type="text" />
</div>
);
}Best Practices for Using Hooks
- Keep Components Simple: Avoid overly complex logic inside hooks. Break down functionality into multiple hooks if necessary.
- Avoid Side Effects in Render: Only use hooks like
useEffectoruseLayoutEffectfor side effects, not in the render function itself. - Custom Hooks: Create reusable logic using custom hooks to keep your components clean and DRY (Don’t Repeat Yourself).
Conclusion
This concludes our discussion on what are React hooks. React Hooks have revolutionized how developers build and manage state in functional components. From basic state management with useState to the more advanced performance optimizations of useTransition and useDeferredValue, hooks have simplified component logic and enhanced scalability. Understanding both the foundational and newer hooks is essential to writing efficient, clean, and maintainable React code.
P.S.: The React is getting popular day by day. If you are curious about the future of React, read here: