React Hooks
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class component.
After this addition, functional component is no longer a stateless component. Now, we can achieve the component behavior using function the way we use to have with class component lifecycle hooks like ComponentDidMount, ComponentWillUnmount, componentDidUpdate etc.. Those where the commonly used important component and we can achieve the same with useEffect hook.
Below are lists of the hooks that got added -
Basic Hooks
useState
Returns a stateful value, and a function to update it. If the new state is computed using the previous state, you can pass a function to setState or a updated value. In case of function - it will receive the previous value, and return an updated value. Here’s an example of a counter component that uses both forms of setState:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
Let's convert class component to functional component to submit firstName an lastName in a form. Below is UserForm class Component.
import React, { Component } from "react";
class UserForm extends Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: ""
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
render() {
const { firstName, lastName } = this.state;
return (
<form>
<input
value={firstName}
onChange={this.handleInputChange}
placeholder="First name"
type="text"
name="firstName"
required
/>
<input
value={lastName}
onChange={this.handleInputChange}
placeholder="Last name"
type="text"
name="lastName"
required
/>
<button type="submit">Submit</button>
</form>
);
}
}
export default UserForm;
Equivalent functional component for the above class component can be written as below -
import React, { useState } from "react";
import "./styles.css";
function UserForm() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
return (
<form>
<input
value={firstName}
onChange={e => setFirstName(e.target.value)}
placeholder="First name"
type="text"
name="firstName"
required
/>
<input
value={lastName}
onChange={e => setLastName(e.target.value)}
placeholder="Last name"
type="text"
name="lastName"
required
/>
<button type="submit">Submit</button>
</form>
);
}
export default UserForm;
We’re basically declaring a state variable and a function to allow us to modify the state variable later. The empty string in the useState call is the initial value of firstName and can be set to any required value. We’ll set it to an empty string for now.
Note that you can name the setFirstName function whatever you want. It is a convention, however, to append ‘set’ before the name of the state variable we’re modifying.
useEffect
useEffect is meant to handle any sort of "side effect" (making a change in some external system, logging to the console, making an HTTP request, etc...) that is triggered by a change in your component's data or in reaction to the component rendering. It replaces componentDidMount, componentDidUnmount, and componentDidReceiveProps, or some code that is run any time your state changes. It can be challenging to grasp the nuances of its use, but by understanding when it runs and how to control that, it can become a little bit easier to wrap your head around.
Run the effect on every render
import React, { useState, useEffect } from "react";
default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`The count is ${count}`);
});
return (
<div>
<p>Count is {count}</p>
<button
onClick={() => {
setCount(count + 1);
}}>
increase
</button>
</div>
);
}
Run the effect only once
Let's say we only wanted the effect to run a single time... think of this as a replacement for componentDidMount. To do this, pass a [] as the 2nd argument to useEffect:
import React, { useEffect } from "react";
default function Mounted() {
useEffect(() => {
console.log("mounted");
}, []);
return <div>This component has been mounted.</div>;
}
Run the effect when data changes
If what you really want is to run the effect only when a specific value changes... say to update some local storage or trigger an HTTP request, you'll want to pass those values you are watching for changes as the 2nd argument. This example will write the user's name to local storage after every time it is updated (triggered by the onChange of the input).
import React, { useState, useEffect } from "react";
default function Listen() {
const [name, setName] = useState("");
useEffect(
() => {
localStorage.setItem("name", name);
},
[name]
);
return (
<div>
<input
type="text"
onChange={e => {
setName(e.target.value);
}}
value={name}
/>
</div>
);
}
Cleaning up from your effect
Sometimes you need to undo what you've done... to clean up after yourself when the component is to be unmounted. To accomplish this you can return a function from the function passed to useEffect... that's a mouthful but let's see a real example, of what would be both componentDidMount and componentDidUnmount combined into a single effect.
import React, { useEffect } from "react";
default function Listen() {
useEffect(() => {
const listener = () => {
console.log("I have been resized");
};
window.addEventListener("resize", listener);
return () => {
window.removeEventListener("resize", listener);
};
}, []);
return <div>resize me</div>;
}
Avoid setting state on unmounted components
Because effects run after the component has finished rendering, and because they often contain asynchronous code, it's possible that by the time the asynchronous code resolves, the component is no longer even mounted! When it gets around to calling the setData function to update the state, you'll receive an error that you can't update state on an unmounted component.
The way we can solve the stated (no pun intended) issue above, is by using a local variable and taking advantage of the "cleanup" function returned from our effect function. By starting it off as true, we can toggle it to false when the effect is cleaned up, and use this variable to determine whether we still want to call the setData function or not.
import React, { useState, useEffect } from "react";
import Axios from "axios";
default function Fetcher({ url }) {
const [data, setData] = useState(null);
useEffect(
() => {
// Start it off by assuming the component is still mounted
let mounted = true;
const loadData = async () => {
const response = await Axios.get(url);
// We have a response, but let's first check if component is still mounted
if (mounted) {
setData(response.data);
}
};
loadData();
return () => {
// When cleanup is called, toggle the mounted variable to false
mounted = false;
};
},
[url]
);
if (!data) {
return <div>Loading data from {url}</div>;
}
return <div>{JSON.stringify(data)}</div>;
}
useContext
useContext makes Context a little easier to consume. The typical way to use the Context API looks like this:
import React from "react";
import ReactDOM from "react-dom";
// Create a Context
const NumberContext = React.createContext();
// It returns an object with 2 values:
// { Provider, Consumer }
function App() {
// Use the Provider to make a value available to all
// children and grandchildren
return (
<NumberContext.Provider value={42}>
<div>
<Display />
</div>
</NumberContext.Provider>
);
}
function Display() {
// Use the Consumer to grab the value from context
// Notice this component didn't get any props!
return (
<NumberContext.Consumer>
{value => <div>The answer is {value}.</div>}
</NumberContext.Consumer>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
See how we get the value inside the Display component? We have to wrap our content in a NumberContext.Consumer and use the render props pattern – passing a function as a child – to retrieve the value and display it.
This works fine, and “render props” is a great pattern for passing dynamic data around, but it does introduce some unnecessary nesting and some cognitive overhead (especially if you’re not used to it).
Let’s rewrite the Display using the new useContext hook and see what it looks like:
// import useContext (or we could write React.useContext)
import React, { useContext } from 'react';
// ...
function Display() {
const value = useContext(NumberContext);
return <div>The answer is {value}.</div>;
}
Additional Hooks
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
An alternative to useState. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method. (If you’re familiar with Redux, you already know how this works.)
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
Here’s the counter example from the useState section, rewritten to use a reducer:
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: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</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 or useCallback dependency list.
Specifying the initial state
There are two different ways to initialize useReducer state. You may choose either one depending on the use case. The simplest way is to pass the initial state as a second argument:
const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);
Note
React doesn’t use the state = initialState argument convention popularized by Redux. The initial value sometimes needs to depend on props and so is specified from the Hook call instead. If you feel strongly about this, you can call useReducer(reducer, undefined, reducer) to emulate the Redux behavior, but it’s not encouraged.
Lazy initialization
You can also create the initial state lazily. To do this, you can pass an init function as the third argument. The initial state will be set to init(initialArg).
It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action:
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
Bailing out of a dispatch
If you return the same value from a Reducer Hook as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
Returns a memoized callback.
Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).
Note
The array of dependencies is not passed as arguments to the callback. Conceptually, though, that’s what they represent: every value referenced inside the callback should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.
We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Returns a memoized value.
Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.
If no array is provided, a new value will be computed on every render.
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
Note
The array of dependencies is not passed as arguments to the function. Conceptually, though, that’s what they represent: every value referenced inside the function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.
We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.
useRef
const refContainer = useRef(initialValue);
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
A common use case is to access a child imperatively:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Essentially, useRef is like a “box” that can hold a mutable value in its .current property.
You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.
However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
In this example, a parent component that renders <FancyInput ref={fancyInputRef} /> would be able to call fancyInputRef.current.focus().
useLayoutEffect
The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Prefer the standard useEffect when possible to avoid blocking visual updates.
Tip
If you’re migrating code from a class component, note useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate. However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.
If you use server rendering, keep in mind that neither useLayoutEffect nor useEffect can run until the JavaScript is downloaded. This is why React warns when a server-rendered component contains useLayoutEffect. To fix this, either move that logic to useEffect (if it isn’t necessary for the first render), or delay showing that component until after the client renders (if the HTML looks broken until useLayoutEffect runs).
To exclude a component that needs layout effects from the server-rendered HTML, render it conditionally with showChild && <Child /> and defer showing it with useEffect(() => { setShowChild(true); }, []). This way, the UI doesn’t appear broken before hydration.
useDebugValue useDebugValue(value)
useDebugValue can be used to display a label for custom hooks in React DevTools.
For example, consider the useFriendStatus custom Hook described in “Building Your Own Hooks”:
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// Show a label in DevTools next to this Hook
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
Tip
We don’t recommend adding debug values to every custom Hook. It’s most valuable for custom Hooks that are part of shared libraries.
Defer formatting debug values In some cases formatting a value for display might be an expensive operation. It’s also unnecessary unless a Hook is actually inspected.
For this reason useDebugValue accepts a formatting function as an optional second parameter. This function is only called if the Hooks are inspected. It receives the debug value as a parameter and should return a formatted display value.
For example a custom Hook that returned a Date value could avoid calling the toDateString function unnecessarily by passing the following formatter:
useDebugValue(date, date => date.toDateString());
useCallback vs useMemo
The APIs of useCallback and useMemo look similar. They both take in a function and an array of dependencies.
useCallback(fn, deps); useMemo(fn, deps);
So what is the difference? useCallback returns its function uncalled so you can call it later, while useMemo calls its function and returns the result.
You want to use useCallback or useMemo whenever you depend on referential equality between renders. In React functions defined in function components get re-created on every render because of closure. And therefore if you are using function as dependency in useEffect this will lead to infinite loop. So to avoid this we should be using useCallback. Below is excellent post explaining the meaning of referential equality highlighting difference between usecallback-vs-usememo.
