10 React Hooks And How To Use Them.
Dev. Zubby Okere
Posted on July 23, 2023
INTRODUCTION
There is no modern React, today, without React hooks. They are a core part of React. React hooks give functional components access to states and other React features. They were introduced in Version 16.8 of React and introduced a better way to write React, use state, and so many features in functional components. React encourages developers to use functional components together with hooks in building projects and to get rid of class components which is more difficult and would mean you have to manage state and lifecycle methods within a class component. Hooks provide a modern way to build components and just like React, gets a lot of support in the React community and ecosystem. With that said, this article is going to be beginners friendly, but would get a bit more complex with more advanced React hooks like useImperativeHandle, useMemo & useCallBack, etc.
React divides all built-in React hooks into the following;
State Hooks (useState & useReducer).
Context Hooks (useContext).
Ref Hooks (useRef & useImperativeHandle).
Effect Hooks (useEffect, useLayoutEffect & useInsertionEffect).
Performance Hooks ( useMemo & useCallBack, useTransition & useDeferedValue).
Other Hooks (useDebugValue, useId & useSyncExternalStore).
State Hooks
1. The useState hook: The useState hook is simply a hook that lets you add a state variable to your component. The state makes a component to “remember” something like a user input or value. For us to use the useState hook, just like every other hook, we have to import useState at the top, into our component, as you can see below;
import { useState } from "react";
After that, you have to initialize state at the top of your functional component, take a look at the code snippet below;
function UpdateCounter() {
const [count, setCount] = useState(0);
}
The convention here is that you’d have a current state and a function to set or update the state. Take a look at the example above for reference and how “count” and “setCount” is used to update the count state.
Below is a simple counter app with “useState.”
import React, { useState } from 'react';
const UpdateCounter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
In the code snippet above, we imported useState, declared/initialized it at the top level of the functional component and then in the increment function, we use the “setCount” to update the count. That’s how we changed the state. Since the state “remembers”, we call the increment function in the button and then, the count is updated as shown in the “p” tag. That’s a very simple and basic example here. Always remember that we can set our initial state to a string, Boolean, number(just like we did above), null, object & array.
2. The useReducer hook: This particular hook is an alternative to the useState hook. It also has to be called at the top of your component, and also, you have to import useReducer from React. Below is a simple counter app with “useReducer”;
import { 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 };
default:
throw new Error();
}
};
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const increment = () => {
dispatch({ type: 'increment' });
};
const decrement = () => {
dispatch({ type: 'decrement' });
};
return (
<div>
<p>Count: {state.count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
There aren’t so many use cases where you use the “useReducer” hook, but it’s also good to know.
Context Hook
3. The useContext hook: This particular hook is necessary for accessing shared data across components without having to do that with props (props drilling). It’s basically a way for you to manage states, globally. When you want to pass states without managing it with “useContext”, you’d have to pass props from one component to another and another. You also have to remember that “useContext” is used alongside the “useState” hook. Take a look at the code below;
import { useState } from "react";
function Component1() {
const [user, setUser] = useState("Mr. Zubby");
return (
<>
<h1>{`Hello ${user}!`}</h1>
<Component2 user={user} />
</>
);
}
function Component2({ user }) {
return (
<>
<h1>Component 2</h1>
<Component3 user={user} />
</>
);
}
function Component3({ user }) {
return (
<>
<h1>Component 3</h1>
<Component4 user={user} />
</>
);
}
function Component4({ user }) {
return (
<>
<h1>Component 4</h1>
<Component5 user={user} />
</>
);
}
function Component5({ user }) {
return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}
Component 2-4 didn’t have any use for the data that they shared. It wasn’t used in the components, but they just had to share it, like a dummy component. It’s finally used in the fifth component. This could be a real issue if you are working on a large project and take for instance, you want to share a user login details across different components, you’d have to go the long route. But with “useContext” hook, you can solve that easily.
To use the “useContext” hook, you’d have to import “createContext” from React and also initialize it. It’s also very important to note that it’s good practice to create a separate file which you can name literally anything you like. Let’s use “contextTuts.js.” Take a look at the updated code below;
import { useState, createContext } from "react";
const UserContext = createContext()
import React, { useState, createContext } from "react";
import Component1 from "./Component1";
import Component2 from "./Component2";
import Component3 from "./Component3";
import Component4 from "./Component4";
import Component5 from "./Component5";
export const UserContext = createContext();
function ContextTutorial() {
const [userName, setUserName] = useState("Okere Josiah");
return (
<UserContext.Provider value={{ userName, setUserName }}>
<Component1/> <Component2 /><Component3/><Component4/> <Component5/>
</UserContext.Provider>
);
}
export default contextTuts;
Using the above code snippet, let’s assume that you created a file called ContextTuts and that other components from 1-5 are different files. Having this in mind, we can now send data to all the five components, and use it in any of the components we want to use the data in. To do that, you have to import the “useContext” at the top and also import UserContext from the ContextTuts file take a look at the code below;
import React, { useContext } from "react";
import { UserContext } from "./ContextTuts";
function Component2() {
const { setUsername } = useContext(UserContext);
return (
<div>
<input
onChange={(event) => {
setUsername(event.target.value);
}}
/>
</div>
);
}
export default Login;
This way, in Component2, a user will be able to set username, easily, because the “setUserName” function has already been sent to it (Component2) directly, without having to pass it around through different components. Now, Component2, just like with other components, is where the username can be set to another username. The “useContext” hook simplifies props drilling.
Ref hooks
4. The useRef hook: The “useRef” hook is easy to understand but there aren’t too many use cases where you’d have to use it. As the name implies, it is used to make a reference or to access and, or manipulate the dom. This is fine without triggering a re-render. So, essentially, the “useRef” hook is important when you don’t have to render or re-render anything in the DOM. To be able to use the useRef hook, you’d have to import it from React, as you can see below;
import { useRef } from 'react';
Then inside your component, you can call the “useRef” hook and pass the argument you want. You can access the value of a ref using the “ref.current property”. The code below explains an example where we can use the “useRef”.
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleIncrement() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleIncrement}>
Click button
</button>
);
}
The “handleIncrement” function is passed to the button to increase “ref.current” when the button is clicked. The “ref” could point to anything at all, it could be an object, a string and a function. According to the React doc, “Unlike state, ref is a plain JavaScript object with the current property that you can read and modify.” Just to reiterate, you should also note that unlike state, the “ref” doesn’t trigger a re-render. To conclude on “useRef”, using another example code below, we can focus on an input by clicking the button beside it and then start typing on the input and we can also clear whatever has been typed in an input by clicking the same button, all done without a re-render. Check the code snippet below;
import React, { useRef } from "react";
function RefTutorial() {
const inputRef = useRef(null);
const onClick = () => {
inputRef.current.focus();
};
return (
<div>
<h1>Pedro</h1>
<input type="text" placeholder="Ex..." ref={inputRef} />
<button onClick={onClick}>Change Name</button>
</div>
);
}
export default RefTutorial;
In the code snippet above, we can focus on the input by clicking on the change name button. In the updated code below, we can clear input;
import React, { useRef } from "react";
function RefTuts() {
const inputRef = useRef(null);
const onClick = () => {
inputRef.current.value = "";
};
return (
<div>
<h1>Josiah</h1>
<input type="text" placeholder="Enter any text..." ref={inputRef} />
<button onClick={onClick}>Change Name</button>
</div>
);
}
export default RefTuts;
In this updated code, we can clear what’s been typed by clicking on the same “change name” button.
5. The useImperativeHandle hook: This hook also falls under the Ref hooks and it is commonly used together with the “useRef” hook. It’s only used in very specific kinds of situations. This hook allows us to define functions based on a “ref” that can be called by using that “ref.” To break it down further, with the “useImperativeHook”, you can perform an action on a component, from outside the component, a separate component. It could be a parent and child component. You could do something, on either component, by referencing the component you want to perform an action on. To get a clearer understanding of how it’s used, check the code snippet below;
import Button from "./Button";
function UseImperativeHandle() {
return (
<div>
<button>
Button From Parent
</button>
<Button/>
</div>
);
}
export default UseImperativeHandle;
In the above code, we have a simple component (parent component) that returns jsx and a “”, which is another component (child component), called inside of it.
And in the code below, we have another component, which is the child component that gets called in the parent component, check the code below;
import React, {useState } from "react";
const Button = () => {
const [toggle, setToggle] = useState(false);
return (
<>
<button onClick={()=> {setToggle(!toggle)}}>
Button From Child
</button>
{toggle && <span>Toggle</span>}
</>
);
};
export default Button;
In the code above, we have a functional component named “Button”, with useState called at the top, initialized as a Boolean “false”. Then we have a simple toggle function in the button. Very simple, you already know that.
Now, to do this differently, we want to set the toggle to true and to false, but from the parent component and not in the child component (Button) in the immediate code above. The updated code show us how to do that below;
import React, { useRef } from "react";
import Button from "./Button";
//parent component below
function UseImperativeHandle() {
const buttonRef = useRef(null);
return (
<div>
<button
onClick={() => {
buttonRef.current.alterToggle();
}}
>
Button From Parent
</button>
<Button ref={buttonRef} />
</div>
);
}
export default UseImperativeHandle;
//child component below
import React, { forwardRef, useImperativeHandle, useState } from "react";
const Button = forwardRef((props, ref) => {
const [toggle, setToggle] = useState(false);
useImperativeHandle(ref, () => ({
alterToggle() {
setToggle(!toggle);
},
}));
return (
<>
<button>Button From Child</button>
{toggle && <span>Toggle</span>}
</>
);
});
export default Button;
In the child component, we simply imported the forwardRef which we used to wrap the Button component (child component) this will enable us to pass data, including props and ref as seen in the code above. Then we imported the useImperativeHandle and set its function, passed the “ref” in it, and also initialized a function called “alterToggle()” and then passed the “setToggle(!toggle)” which is used to change the state to its opposite (remember this is done in the parent component called useImperativeHandle).
Then remember in the parent component above, we setup a function in the button which will call the alterToggle function that was created in the Button component (child component). We can now access the function by using “buttonref.” Remember that this “buttonref” was initialized in the parent component which is the UseImperativeHandle, and that’s how we are going to reference the alterToggle function. That way, we can now alter the value of the state, even though the state is set up in the child component (Button). So, we are now performing an action on a different component(Button), from another component entirely(UseImperativeHandle). This is similar to the “useRef” hook but in this case we are referencing another component.
Effect Hooks
6. The useEffect hook: This is a really popular hook, it’s almost as popular and commonly used as the “useState” hook and it’s actually used together with the “useState” hook, most of the time. This hook is mostly used when we want to fetch data or connect to an external database. The “useEffect” hook simply connects a component to an external system. It’s used to fetch data, for cleanup functions, dependencies and re-renders, event listeners, etc. for this article, an example of how to use the “useEffect” hook to fetch data will be shown here, because that’s the most common use of the “useEffect” hook. To learn more about its different uses, click here. How to use the “useEffect” hook to fetch data from a database and store it in a state will be shown in the code snippet below;
import React, { useState, useEffect } from 'react';
Const EffectComponent=()=>{
const [data, setData] = useState([]);
useEffect(() => {
// Fetching data from an external API
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
}, []);
// the rest of the code for your component goes in here
}
Export default EffectComponent;
The empty dependency array in the code above “[]” tells the effect to run only once immediately the component mounts. It will be a costly mistake to avoid putting it because you’d end up making endless calls and that wouldn’t be good for your project.
With the “setData” function, we store the data that has been fetched in the “state” above called “data” and that’s why the “useState” and “useEffect” are usually imported together.
7. The useLayoutEffect hook: This is a variation of the “useEffect”, though it is rarely used. According to the React documentation, there’s one pitfall: “useLayoutEffect can hurt performance.” They also said we should “Prefer useEffect when possible.”
The “useLayoutEffect” hook happens synchronously after all DOM mutations have already happened but before the browser repaints the screen. It’s best used for tasks that need access to the DOM and also measuring layout before any content is displayed on the screen. Since it runs before the browser repaints the screen, it’s commonly used for measuring layout, like calculating the dimension of an element.
In the code snippet below, “useLayoutEffect” is used to measure the height of an element and also update the state with the height of that element. Let’s have a look at the code snippet below;
import React, { useState, useLayoutEffect, useRef } from 'react';
function HeightMeasurement() {
const [height, setHeight] = useState(0);
const elementRef = useRef(null);
useLayoutEffect(() => {
// Measurement for the height of the element
const exactHeightMeasurement = elementRef.current.clientHeight;
setHeight(exactHeightMeasurement);
}, []); //The empty dependency array makes sure this runs only once, similar to componentDidMount
return (
<div ref={elementRef}>
<p>The measured height of this element is: {height}px</p>
{/* ... Any other content... */}
</div>
);
}
Remember the useRef was also imported to make a reference to the div element. To study more on useEffectLayout, click here. Just to recap, the “useEffect” is almost one hundred percent more performant than the “useLayoutEffect” and also the “useLayoutEffect” runs synchronously after React has performed every DOM mutation but just before the browser repaints the screen.
Performance hooks
8. The useMemo hook: This is a more advanced hook which is used to enhance the performance of our application. It’s used when complex computation is done. Think of it as value caching just to prevent re-computation. For example, when you have a function that takes some data or input and returns some result, the “useMemo” will be useful in “remembering” the result, provided the input doesn’t change. If inputs remain the same, React will return the “memoized” version instead of a re-computation happening. This re-computation could be very expensive, especially when dealing with a lot of data, and also reduce the performance of your React application.
In the “useMemo” hook, there is a dependency array which contains dependencies. If any of the dependencies change, the function in “useMemo” will execute another time and this will return a new “memoized” result. Take a look at the code snippet below to get a clearer understanding;
import { useState } from "react";
const ExpensiveComponent = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const computation = expensiveComputation(count);
const incrementCount = () => {
setCount((e) => e + 1);
};
const addTodo = () => {
setTodos((c) => [...c, "New Todo"]);
};
return (
<div>
<div>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</div>
<br/>
<div>
Count: {count}
<button onClick={incrementCount}>+</button>
<h2>Expensive Calculation</h2>
{computation}
</div>
</div>
);
};
const expensiveComputation = (num) => {
console.log("Computing...");
for (let i = 0; i < 1000000000000; i++) {
num += 1;
}
return num;
};
So, in the code snippet above, the “expensiveComputation” function will always run when a new todo is added, which isn’t healthy practice, performance wise. To fix this issue, in the updated code snippet below, the “useMemo” is used to wrap the “expensiveComputation” function call and a dependency array will be added which comes in as a second parameter. The “expensiveComputation” function will only run when its dependencies change. This means that the “expensiveComputation” function will run when the “count” state is changed and not when a new todo is set. Kindly reference the updated code below;
import { useState } from "react";
const ExpensiveComponent = () => {
const [count, setCount] = useState(0);
const [todo, setTodo] = useState([]);
const computation = useMemo(()=>expensiveComputation(count), [count]);
const incrementCount = () => {
setCount((e) => e + 1);
};
const addTodo = () => {
setTodo((c) => [...c, "New Todo"]);
};
return (
<div>
<div>
<h2>My Todos</h2>
{todo.map((item, index) => {
return <p key={index}>{item}</p>;
})}
<button onClick={addTodo}>Increment Todo</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={incrementCount}>+</button>
<h2>Expensive Calculation</h2>
{computation}
</div>
</div>
);
};
const expensiveComputation = (num) => {
console.log("Computing...");
for (let i = 0; i < 1000000000000; i++) {
num += 1;
}
return num;
};
export default ExpensiveComponent;
Also note that “computation…” will log only once to the console when the “addTodo” button is clicked, but it will log to the console as count is increased, because the “useMemo” hook is used this time around.
Summarily, the whole point of the “useMemo” hook is to increase performance and decrease latency.
9. The useCallBack hook: The “useCallBack” hook is very similar to the “useMemo” hook, but the very slight difference is that, while the “useMemo” hook is also used for “memoization”, it returns a value. The “useCallback” hook on the other hand returns a function. The “useCallback” hook also takes a function and a dependency array, but returns a “memoized” version of a function.
Just like the “useMemo” hook, the “useCallback” hook is also used for optimization and performance enhancement, and avoiding unnecessary re-renders. This is necessary especially when passing down functions as props, from parent component to child component, because a change in function reference in the parent component, for example, can trigger a re-render in the child component, even if nothing has changed in the function logic. Also, remember that the dependency array in the “useCallback” is optional. Once there’s a change in the dependency array, then there will be a re-render. That will be the only condition for a re-render. If there isn’t a dependency array, then the function will be “memoized” on the initial render and subsequent re-render. The code snippet below shows how it’s used;
// Parent.js file
import { useCallback } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const [count, setCount] = useState(0);
const handleCount = () => {
setCount((prevCount) => prevCount + 1);
};
return (
<div>
<button onClick={handleCount}>Increment Count</button>
<ChildComponent onClick={handleCount} />
</div>
);
};
export default ParentComponent;
In the above code, whenever the button is clicked and the “count” state incremented, there will be a re-render in the child component which is imported into the parent component called “ ParentComponent” and a new version of the “handleCount” function will be returned, even though nothing’s changed in the “handleCount” logic. This happens because there’s a change in the state of the parent component and the parent component also re-renders. To prevent this expensive re-render in the child component, we use the “useCallback” hook. The updated code below shows how to use it and prevent unnecessary re-renders;
// Parent.js
import { useCallback } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const [count, setCount] = useState(0);
const handleCount = useCallback(() => {
setCount((prevCount) => prevCount + 1)
},[]);
return (
<div>
<button onClick={handleCount}>Increment Count</button>
<ChildComponent onClick={handleCount} />
</div>
);
};
export default ParentComponent;
Now, with this updated code, “handleCount” will now be “memoized” and the same version or reference of the logic will be retained between re-renders unless there’s a change in the dependency array. This means that the child component will not re-render even if the parent component re-renders as a result of the state change.
Finally, if the dependency array is empty, the function will be memoized on initial render and there wouldn’t be any change in between renders. You can add dependencies to the array to suit the result you want. The one in the code above is empty.
10. The useTransition hook:* This is a pretty recent hook, included in React 18 and also classified as a performance hook by React. It’s a hook that lets you update the state of an application without blocking the UI. It can be used to improve the performance and responsiveness of your application. This hook is really easy to implement. Though not very popular and not used as much as others, it’s still very useful. It tells React to treat some state changes as not so important. That means the rendering of a component can happen, while the “useTransition” hook is used to render other state changes much later and without blocking the UI. The code snippet below explains how to implement it better;
import React, { useState, useTransition } from "react";
const Example = () => {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition({
duration: 2000,
easing: "ease-in-out",
});
const handleClick = () => {
startTransition(() => {
setCount(count + 1);
});
};
return (
<div>
<button onClick={handleClick}>Click me</button>
{isPending ? ( <p>Loading…</p>
) : ( <p>The count is {count}</p>})
</div>
);
};
In the code snippet above, when the button is clicked, the “startTransition” which is from the “useTransition” hook will be triggered and it will update the count state after 2000 milliseconds. So the “useTransition” hook is used to update the state of the count variable. Then the “isPending” which is a part of the “useTransition” array is a Boolean which is used to indicate whether the “startTransition” is pending or has been executed. Of course, we have to import the “useTransition” hook from React and every other thing is as easy as it gets!
Other Hooks:
The useInsertionEffect hook: This hook is a version of the “useEffect” hook that fires before any DOM mutation happens. According to the React documentation, they said there’s one pitfall; “useInsertionEffect is for CSS-in-JS library authors. Unless you are working on a CSS-in-JS library and need a place to inject the styles, you probably want useEffect or useLayoutEffect instead.” To learn more about it, click here.
The useDeferedValue hook: This particular hook, as the name implies, is used when you want to defer updating a part of the UI. Click here to learn more about it.
The useDebugValue hook: According to the React documentation, “useDebugValue is a React Hook that lets you add a label to a custom Hook in React DevTools.” Click here to learn more about it.
The useId hook: This is a hook that is used for generating unique IDs that can be passed to accessibility attributes, according to the React documentation. Read about it here.
The useSyncExternalStore hook: This is a hook that allows Components subscribe to an external store. More about that here.
In conclusion, with the introduction of React hooks since version 16.8, hooks have revolutionized the way we write React code. With hooks, the need for class components isn’t there anymore because it’s now easier to reuse and share state logic. It has also enchanted the readability and maintainability of code. Today, there’s no React without React hooks.
Kindly like, comment and share your thoughts. Also follow me on Twitter here.
Posted on July 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.