Hooks were introduced to React 16.8. Hooks allow you to use things like state without writing a class-based component. In essence, Hooks are JavaScript functions that allow you to "hook into" React state and lifecycle features from function components. There are a number of built-in Hooks as well as the ability to write your own custom Hooks.
Note: It's important to remember that Hooks are backwards compatible, so you can use them with class components without a problem.
All React Hook names are prefixed with the word "use," like useState
or useEffect
. To use Hooks, you must import them first. You can enter any number of Hooks at the top of the file like so:
import React, { useState, useEffect } from "react";
Why use hooks?
When compared to class components, hooks reduces the amount of code we need to write (and read). Take a look at this example:
With Hooks we can organize logic inside of a component. These units can be reusable, helping us avoid duplicated logic. Dan Abramov writes that "Hooks apply the React philosophy (explicit data flow and composition) inside a component, rather than just between the components." Hooks are fully encapsulated, and are a way to share stateful logic.
Here are some reasons to use Hooks from Shedrack Akintayo
- Improved code reuse
- Better code composition
- Better defaults
- Sharing non-visual logic with the use of custom Hooks
- Flexibility in moving up and down the components tree
Rules
There are two important rules for using Hooks:
Only call Hooks at the top level. Don't call Hooks inside of loops, conditions, or nested functions.
Only call Hooks from React functions. Do not call Hooks from regular JavaScript functions. You can and should call Hooks from React function components. You may also call Hooks from other custom Hooks.
There is a plug-in called esLint which will enforce the rules of Hooks. Find and install it here!
You can use multiple Hooks in a single component, and React will rely on the order in which Hooks are called. Dan Abramov also states:
The ability to pass data between Hooks make them a great fit for expressing animations, data subscriptions, form management, and other stateful abstractions. Unlike render props or higher-order components, Hooks don’t create a “false hierarchy” in your render tree. They’re more like a flat list of “memory cells” attached to a component. No extra layers.
Built-in Hooks
React has 10 built-in Hooks, here are some of the more commonly used ones:
useState
The useState
Hook is called inside of a function component to add local state to it. It allows you to create, update, and manipulate state inside the functional component it is used in. useState
returns a destructured array containing two values: the first is the current state; the second is a setter function used to update the state. useState
takes a single argument, which is it's initial state.
const [state, setState] = useState(initialState);
Here is an example of a simple counter:
import React, { useState } from "react";
function Counter(){
// The new state variable is called "count" here, and the initial value will be 0 since the counter will begin at 0.
const [count, setCount] = useState(0);
return (
<div>
<p>The button was clicked {count} times!</p>
<button onClick={() => setCount(count + 1)}>
Click to Count
</button>
</div>
);
}
The new state variable is called count
, and the setter function is called setCount
. You can call these two variables anything you'd like, but this is the typical naming convention: use "set" concatenated with the variable name. The initial state is set to 0 since we want the counter to begin at 0. The setCount
function will be called each time the button is clicked, and update the count variable by 1. React will remember the current value of count
between re-renders, and provide the most recent one to the function component. If we want to update it, we just call setCount
again.
As you can see, setCount
was called inside of the function's return, namely in the JSX code of the button's onClick
. This is perfectly fine!
You can use the state
Hook multiple times in a single function component:
function ManyStatesExample() {
const [name, setName] = useState("Jimmy");
const [age, setAge] = useState(35);
const [favoriteColors, setFavoriteColors] = useState(["red", "blue"]);
Note: Updating a state variable replaces it's contents.
useEffect
The useEffect
Hook replicates React's lifecycle methods, but inside of a functional component. The official React documentation states: "If you’re familiar with React class lifecycle methods, you can think of useEffect
Hook as componentDidMount
, componentDidUpdate
, and componentWillUnmount
combined." The useEffect
Hook lets you perform side effects (such as external API interactions and data fetching). useEffect
accepts two arguments:
- A function with the code to run
- A dependency array, which tells the Hook how many times to run. If this is left out, the Hook will run after every render. Be careful, leaving out the dependency array can cause infinite loops!
If you only want to run the useEffect
Hook when certain values in your function have changed, you must pass the variables as dependencies into the array. If you supply an empty array, the Hook will only run once. This can be very useful for things such as fetch requests:
useEffect(() => {
fetch("http://somelink.com");
}, []);
The dependency array is empty because we only need to GET our data once.
Sometimes the useEffect
Hook can require cleanup, which is written about in some detail in the React docs.
useContext
The useContext
Hook allows you to make particular data accessible to all components throughout the whole of the application (consider it "global" in scope for a tree of React components). It works with the React Context API. This Hook is powerful because data can be passed across all components in an app, rather than having to manually pass a prop down through various levels to get data to a child.
For example, let's say you have an array of objects, and would like to pass that information down to a child function.
const menuItems = [
{
dish: "spaghetti and meatballs",
price: "$12.99",
},
{
dish: "steak and potatoes",
price: "$29.99",
},
];
export const MenuContext = React.createContext(menuItems);
We are creating a context object using React.createContext
and now can pass menuItems
to any components in the app. Each context needs to be enclosed in a Provider wrapper, like so:
function Menu() {
return (
<MenuContext.Provider value={menuItems}>
<DinnerMenu />
</MenuContext.Provider>
);
}
Now DinnerMenu
becomes the consuming component of the context, and can use the objects stored in the MenuItems array. When the Provider updates, the Hook will trigger a rerender with the latest context value. See React's advanced Context guide here.
useReducer
useReducer
can be thought of as an alternative to the useState
Hook. The useReducer
Hook can work with more complex logic, and lets you rerender the UI whenever values change within the Hook. It accepts two arguments: a reducer function and an initial state.
useReducer(reducerFunction, initialState);
Much like useState
, useReducer
returns an array of two values, which can be destructured to: the current value of the state; and a dispatch function.
const [state, dispatchFunction] = useReducer(reducerFunction, initialState);
state
is the current value of the initialState passed to the Hook. The reducerFunction
accepts the state and an action. These two arguments will determine how the value of the state will change. The dispatchFunction
is how an action is passed to the reducerFunction
. There is usually a switch statement with a number of cases to determine how the value of state will change. Here is an example of a simple counter from the official documentation:
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:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
Note: React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the
useEffect
oruseCallback
dependency list.
useRef
The useRef
Hook allows you to persist values between renders. You can store a mutable value that does not cause a re-render when updated. useRef
returns an Object called current
. Consider useRef
like a "box" that can hole a mutable value in this current
property.
You can use this Hook to keep track of previous state values, because useRef
persists it's values between renders. Consider the following example from W3Schools:
import { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom/client";
function App() {
const [inputValue, setInputValue] = useState("");
const previousInputValue = useRef("");
useEffect(() => {
previousInputValue.current = inputValue;
}, [inputValue]);
return (
<>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<h2>Current Value: {inputValue}</h2>
<h2>Previous Value: {previousInputValue.current}</h2>
</>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
See this blog post by Dmitri Pavlutin for more information and use cases of useRef
.
Other Built-in Hooks
- useCallback
- useMemo
- useImperativeHandle
- useLayoutEffect
- useDebugValue
Build Your Own Hooks
Along with these Hooks you can write your own custom Hooks that let you extract component logic into reusable, sharable functions. Because both components and Hooks are functions, you can extract the logic to a third, shared function (a new Hook!). A custom Hook is a JavaScript function whose name starts with "use" and that may call other Hooks. It still follows the rules of built-in Hooks, but you get to decide which arguments it will take and what it should return.
For more information on building custom Hooks, see: Building Your Own Hooks.
Latest comments (0)