January 10, 2024
Hooks are essential tools for React developers.
They allow you to easily add interactivity and transform a static website into an engaging and user-friendly experience.
But what exactly are hooks, and how can you use them effectively?
In this post, you’ll learn everything you need to know about hooks, with examples and best practices.
What Are Hooks?
Hooks are built-in functions in React that give you access to various features.
They are recognizable by their prefix, use
and include useState
, useEffect
, useContext
... and many more.
But before exploring the existing Hooks, why do we need them in the first place?
Why hooks?
React Hooks address several challenges encountered in React development.
The story goes like this:
React originally only supported Class Components.
Function Components came in React 16.8 , with the intention to simplify the creation of stateless components.
With Function Components, it takes less (boilerplate) code to achieve the same thing.
import React, { Component } from "react"; // 😒 with class component: class HelloClassComponent extends Component { render() { return ( <p>Hello World!</p>); } } // 💪 with function component: // ✅ less code, more readable const HelloFunctionComponent = () => <p>Hello World!</p>;
So Hooks were introduced for two main reasons:
- empowering the function components
- address the problems emerging from using class components
Problem #1: Reusable Stateful Logic
React originally lacked a method to easily reuse code with class components.
This lead to complex anti-patterns like the render props and higher-order components.
Hooks allow extracting logic from components to functions.
This means easier code sharing and testing.
Problem #2: Complex Component Management
Big, complex components became hard to understand due to a mix of unrelated logic in lifecycle methods.
With hooks complex logic can be broken down to smaller, reusable & combinable functions.
Problem #3: OOP vs Functional Programming
Classes in React introduce barriers to learning and can lead to confusion, especially regarding the use of
this
in JavaScript (Not as simple as you might think).
Hooks eliminate the need for classes, providing a more straightforward approach to React development.
Beginners also don’t need to learn Object-Oriented Programming to get started with react.
React Hooks
The must-know React Hooks are useState
, useEffect
, useContext
, useMemo
and useCallback
.
useState
useState
is arguably the most used hook in React.
It allows you to define a variable in a component, that can be used when rendering the ui.
Take this simple toggle as an example:
We can use a state variable to achieve this effect with useState
:
import { useState } from "react";
import { Switch, Text } from "@mantine/core";
function Toggle() {
const [toggled, setToggled] = useState(false);
return (
<Text as="label" size="2">
<Switch checked={toggled} onClick={() => setToggled(!toggled)} />
{toggled ? "On": "Off"}
</Text>
);
}
Explanation: We use the useState
hook to define a variable named toggled
that is used to render the correct icon.
useState
takes the initial value as an argument (in this case true
)
and returns a variable and an update function to set the value.
Each time the icon is clicked, toggle
's value is flipped (true→false→true).
The state is used to conditionaly render the correct icon "ToggleLeft"
or "ToggleRight"
.
useEffect
The useEffect hook in React is used for side effects in functional components.
Here's the basic syntax:
useEffect(() => { // Side effect code goes here }, [dependencies]); // This code will run each time the dependencies change
Let's break down the components of this syntax:
useEffect
: This is the hook provided by React to perform side effects in functional components.() => { /* Side effect code */ }
: This is the function that contains the code for the side effect. It will run after the component renders. The function can return a cleanup function (optional), which will be executed before the component unmounts or before the next useEffect execution.[dependencies]
: This is an optional array of dependencies. If provided, the effect will only re-run if any of the dependencies have changed between renders. If no dependencies are provided ([]
), the effect will only run once.
This can be used to cover many use cases.
Example 1: loading data from a server
Checkout this simple example where you can see a GitHub users details:
👇Enter your Github handle
Loading...
import React, { useState, useEffect } from "react";
const GithubProfile = ({ username }) => {
const [profileData, setProfileData] = useState(null);
useEffect(() => {
const fetchProfile = async () => {
try {
const response = await fetch(`https://api.github.com/users/${username}`);
const data = await response.json();
setProfileData(data);
} catch (error) {
console.error("Error fetching GitHub profile:", error);
}
};
fetchProfile();
// depends on username.
// the effect will run each time it changes
}, [username]);
return (
<div>
{profileData ? (
<div>
<h2>{profileData.login}</h2>
<img src={profileData.avatar_url} alt={`${profileData.login}'s avatar`} />
<p>{profileData.bio}</p>
</div>
) : (
<p>Loading...</p>
)}
</div>
);
};
export default GithubProfile;
useContext
The useContext
hook allows you to access a React context inside a function component.
Think of context as a global state that is available to all its children, eliminating the need for "prop drilling"—the tedious process of passing props down multiple layers just to reach a deeply nested component.
Example: Theme Context
Imagine a simple app where you want to toggle between light and dark themes. Using useContext
, you can manage and share this theme state easily across components:
import React, { createContext, useContext, useState } from "react"; const ThemeContext = createContext(); const ThemeProvider = ({ children }) => { const [theme, setTheme] = useState("light"); const toggleTheme = () => setTheme(theme === "light" ? "dark" : "light"); return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); }; const ThemeSwitcher = () => { const { theme, toggleTheme } = useContext(ThemeContext); return ( <button onClick={toggleTheme}> Switch to {theme === "light" ? "dark" : "light"} mode </button> ); }; const App = () => ( <ThemeProvider> <ThemeSwitcher /> </ThemeProvider> ); export default App;
With useContext
, any component inside ThemeProvider
can access the theme state without explicitly passing it through props!
useMemo
useMemo
is used to optimize performance by memoizing values—only recalculating them when their dependencies change.
Example: Avoiding Unnecessary Computations
Imagine you have a function that processes a large dataset. You don't want it to run on every render unless necessary:
import React, { useState, useMemo } from "react"; const slowFunction = (num) => { console.log("Running slow function..."); return num * 2; }; const MemoExample = () => { const [count, setCount] = useState(0); const [number, setNumber] = useState(10); const computedValue = useMemo(() => slowFunction(number), [number]); return ( <div> <h2>Computed Value: {computedValue}</h2> <button onClick={() => setNumber(number + 1)}>Change Number</button> <button onClick={() => setCount(count + 1)}> Re-render (Count: {count}) </button> </div> ); }; export default MemoExample;
The useMemo
hook ensures that slowFunction
only runs when number
changes, rather than on every re-render.
useCallback
Similar to useMemo
, useCallback
memoizes functions to prevent unnecessary re-creations.
Example: Preventing Unnecessary Function Re-Creation
When passing functions as props to child components, React re-creates them on every render. This can cause unnecessary re-renders. useCallback
helps avoid that:
import React, { useState, useCallback } from "react"; const Button = React.memo(({ handleClick }) => { console.log("Button re-rendered"); return <button onClick={handleClick}>Click Me</button>; }); const CallbackExample = () => { const [count, setCount] = useState(0); const increment = useCallback(() => { setCount((prevCount) => prevCount + 1); }, []); return ( <div> <h2>Count: {count}</h2> <Button handleClick={increment} /> </div> ); }; export default CallbackExample;
Without useCallback
, increment
would be recreated on every render, causing the Button
component to re-render unnecessarily.
Rare Hooks
While useState
, useEffect
, useContext
, useMemo
, and useCallback
are the most commonly used hooks, React provides several other hooks for specific use cases:
useRef
– For accessing and persisting DOM elements or values across renders without causing re-renders.useImperativeHandle
– Customizes aref
instance when usingReact.forwardRef
.useLayoutEffect
– Similar touseEffect
, but runs synchronously after all DOM mutations.useDebugValue
– Adds labels for custom hooks in React DevTools.useTransition
– Helps manage UI transitions smoothly.useDeferredValue
– Defers rendering of a value to prevent UI lag.useSyncExternalStore
– For subscribing to external stores.useId
– Generates unique IDs useful for accessibility attributes.
What’s Next?
Hooks have revolutionized how we build React applications by making code more readable, maintainable, and reusable.
The next step is mastering custom hooks! Learn how to create your own hooks to encapsulate reusable logic and supercharge your React apps.
🚀 Check out our guide on Creating Custom Hooks in React to take your skills to the next level!