Hooks are a new addition in React 16.8. They let you use state and other React features without writing class components.

Let’s explore them in-depth in this article.

What are hooks?

Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don’t work inside classes — they let you use React without classes. In fact, they can only be used in functional components.

What’s the motivation behind hooks?

  • It’s hard to reuse stateful logic between components. Hooks allow you to reuse stateful logic without changing your component hierarchy.
  • Complex components become hard to understand. Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.
  • Classes confuse both people and machines. Hooks let you use more of React’s features without classes.

Hooks are JavaScript functions, but they impose two additional rules:

  • Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
  • Only call Hooks from React function components. Don’t call Hooks from regular JavaScript functions or class components. (There is just one other valid place to call Hooks — your own custom Hooks. More on this later.)

In my opinion, the following 4 React Hooks would cover 99% of the use cases:

  • 📌 State Hook — useState
  • ⚡️ Effect Hook — useEffect
  • ⭕️ Context Hook — useContext
  • ☝️Ref Hook — useRef

I would classify these as Hooks in-depth:

  • useReducer
  • useMemo — performance optimisation
  • useCallback — performance optimisation
  • useLayoutEffect
  • useImperativeHandle

📌 State Hook — useState

useState allows us to make our components stateful. We call it inside a function component to add some local state to it.

Whereas using state previously required using a class component, hooks give us the ability to write it using just functions. It allows us to have more flexible components.

React will preserve this state between re-renders.

  • useState hooks maintain state by being fed the latest state in a new every render.
  • useState returns a tuple pair: the current state value and a function that lets you update the state.
  • The only argument passed to useState is the initial state. Unlike this.state, the state here doesn’t have to be an object — although it can be if you want. The initial state argument is only used during the first render.
  • You can have as many hooks in a function you can possibly want; so multiple pieces of state each with their own updater function.

Example of useState— when you click the button, it increments the value:

import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
  return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

Equivalent Class example — if you used classes with React before, this code should look familiar:

class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
  render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}

React Hooks in-depth > useState - CodeSandbox

You can also useState more than once in a single component:

function ExampleWithManyStates() {
// Declare multiple state variables!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}

If the new state is computed using the previous state, you can pass a function to setState. The function 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>
</>
);
}

Unlike the setState function found in class components, useState does not automatically merge update objects. You can replicate this behaviour by combining the function updater form with object spread syntax:

setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});

Lazy initial state — The initialState is the state used during the initial render and in subsequent renders, it is disregarded.

If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render:

const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});

⚡️ Effect Hook — useEffect

You’ve likely performed data fetching, subscriptions, or manually changing the DOM from React components before. We call these operations “side effects” (or “effects” for short) because they can affect other components and can’t be done during rendering.

The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.

  • When you call useEffect, you’re telling React to run your “effect” function after flushing changes to the DOM. Effects are declared inside the component so they have access to its props and state. By default, React runs the effects after every render — including the first render.
  • React is constantly scheduling this.
  • You can also use a second parameter to give it a list of dependencies. An empty array as a second parameter would run the useEffect function once.
  • Effects may also optionally specify how to “clean up” after them by returning a function. So if using setTimeout return clearTimeout. This is so that on unmount React can run the clean-up functions. So with Ajax requests, you can cancel a request.
  • The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that’s what they represent: every value referenced inside the effect function should also appear in the dependencies array.

Example of useEffect — this component sets the document title after React updates the DOM:

import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
  // Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
  return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

Just like with useState, you can use more than a single effect in a component:

function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);

useEffect(() => {
document.title = `You clicked ${count} times`;
});
  const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
Cht.subscribeToStatus(props.friendId, handleStatusChange);
return () => {
Cht.unsubscribeFromStatus(props.friendId, handleStatusChange);
};
});
  function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...

⭕️ Context Hook — useContext

Context solves the problem of “prop drilling” and covers things normally Redux would like controlling application state. It allows you to create values that are available globally.

Side-note on “prop drilling” — this is when you have a top-level component and a child component way down in the hierarchy that needs the same data. You would pass that data down, parent-to-child, for each of the intermediary components but that sucks because now each layer of in component hierarchy have to know about the object being passed down even when they themselves don’t need it, just their children.

To create a context, we can use createContext function — passing the shape of the object and an updater function (technically, its called identity function).

We put a Provider on top of our top-level component and use useContext to read the context at the level the component requires the data from our top-level component.

Basically, context allows you to create a wormhole where stuff goes in and a wormhole in a child component where that same data comes out and the stuff in the middle doesn’t know it’s there. Now that data is available anywhere inside of the Provider. useContext just pulls that data out when given a Context object as a parameter. We don't have to use useState and useContext together (the data can be any shape, not just useState-shaped).

In general, context adds a decent amount of complexity to an app. A bit of prop drilling is fine.

Before hooks were introduced, this is an example of how we would implement context:

React Hooks in-depth > React\'s Context API - CodeSandbox

With Hooks, voila!:

React Hooks in-depth > useContext - CodeSandbox

☝️Ref Hook — useRef

This and the following hooks get a bit harder to follow but please bear with me.

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.

See this example CodeSandBox to illustrate a problem and why useRef is handy:

React Hooks in-depth > useRef - CodeSandbox

In our component, when a user clicks the “delay logging” button, it sets a timeout to log both the state and the ref’s number after a second. One thing to keep in mind that the state and the ref’s number are always the same. They are never out of lockstep since they’re updated at the same time.

However, since we delay the logging for a second, when it alerts the new values, it will capture what the state was when we first called the timeout (since it’s held on to by the closure) but it will always log the current value since that ref is on an object that React consistently gives the same object back to you. Because it’s the same object and the number is a property on the object, it will always be up to date and not subject to the closure’s scope.

Refs are useful where you have multiple closure problems where you want to hold on to the same state. Use useRef to refer to the exact right one and it can be called in the exact right context.

Why is this useful? It can be useful for things like holding on to DOM objects,setInterval and setTimeout IDs so they can be cleared later. Or any bit of statefulness that could change but you don't want it to cause a re-render when it does.

If you are doing a class component, you would be using an instance variable on the component and that’s how you would handle that.

useRef gives us an object and this object has one thing on it called current . It will actually error if you try and add any other property — literally, limits you to only use current by sealing the object.

Essentially, useRef is like a “box” that can hold a mutable value in its .current property.

Keep in mind that useRef doesn’t notify you when it’s content changes.

Mutating .current doesn’t cause a re-render.

useReducer

useReducer allows us to do Redux-style reducers but inside a hook.

Here, instead of having a bunch of functions to update our various properties, we have one reducer that handles all the updates based on an action type. This is a preferable approach if you have complex state updates or if you have a situation like this: all of the state updates are very similar so it makes sense to contain all of them in one function.

As an aside, the term reducer is a function that takes in an old state, takes some sort of action and returns a new state.

With useReducer you pass in an action and the initial state. With Redux, you expect the reducer to be run the first time but this does not. It gives us back two things: the new state and a dispatch function which allows us to dispatch a function to the reducer.

const [state, dispatch] = useReducer(reducer, initialArg, init);

Here’s the Counter example from the useState above, rewritten to use a reducer:

React Hooks in-depth > useReducer - CodeSandbox

useMemo

useMemo and useCallback below are primarily for performance optimisations.

Use them only when you already have a performance problem instead of pre-emptively. It adds unnecessary complexity otherwise.

useMemo memoizes expensive function calls so they only are re-evaluated when needed.

The way React works is if you re-render a parent component you will also re-render all the child components as well.

With useMemo you give it a function on how to compute something and you give its dependencies and it will only recompute it when its dependencies have changed.

const memoizedValue = useMemo(()=>computeExpensiveValue(a, b),[a,b])

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.

If no array is provided, a new value will be computed 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.

Let us demonstrate with this code sandbox.

React Hooks in-depth > useMemo - CodeSandbox

Notice this experience:

useMemo — happy example

We start with an expensive computation — Fibonacci(40) — before we do anything, if we click the useMemo Example heading, we can see that the heading text colour changes from green to red.

However, when we hit the increment button again — Fibonacci(41) — it takes a while for the computation to occur. Clicking on heading after the computation is done, the colour changes instantaneously.

If we swap,

const fib = useMemo(() => fibonacci(num), [num]);

with

const fib = fibonacci(num)

we will notice that the user experience is much poorer — clicking on heading text does not change colour instantaneously like before — because the expensive computation is done for each render.

useMemo — sad example

useCallback

useCallback is quite similar to useMemo and indeed it's implemented with the same mechanisms as useMemo.

const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);

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).

Typically whenever React detects a change higher-up in an app, it re-renders everything underneath it. This normally isn’t a big deal because React is quite fast at normal things. However, you can run into performance issues sometimes where some components are bad to re-render without reason.

Let’s look at our code sandbox.

React Hooks in-depth > useCallback - CodeSandbox

We’re using a new feature of React called React.memo. This is similar to PureComponent where a component will do a simple check on its props to see if they've changed and if not it will not re-render this component (or its children, which can bite you.) React.memo provides this functionality for function components.

Given that, we need to make sure that the function itself given to ExpensiveComputationComponent is the same function every time. We can use useCallback to make sure that React is handing the same fibonacci to ExpensiveComputationComponent every time so it passes its React.memo check every single time. Now it's only if count changes will it actually re-render (as evidenced by the time.)

Note that, React’s memo and useMemo are different things.

What memo means is as long as none of the properties have changed, don’t re-render.

With useCallback you give it a function and dependencies of how often you want that function to update.

Notice this experience:

useCallback — happy example

The timer counts on and when we click the button, it computes the fibonacci sequence and updates the computed and last re-render value.

However, if we swap in the code sandbox,

<ExpensiveComputationComponent
compute={useCallback(fibonacci, [])}
count={count}
/>

with

<ExpensiveComputationComponent compute={fibonacci} count={count} />

We will notice that the user experience is much poorer because the expensive computation is done for all render. The timer is not ticking and last re-render is painfully lagging behind.

useCallback — sad example

useLayoutEffect

useLayoutEffect is almost the same as useEffect except that it's synchronous to render as opposed to scheduled like useEffect is. If you're migrating from a class component to a hooks-using function component, this can be helpful too because useLayoutEffect runs at the same time as componentDidMount and componentDidUpdate whereas useEffect is scheduled after.

The only time you should be using useLayoutEffect is to measure DOM nodes for things like animations. In the example, we measure the textarea after every time you click on it (the onClick is to force a re-render.) This means you're running render twice but it's also necessary to be able to capture the correct measurements.

useLayoutEffect is pretty much only useful when you need to measure things in the DOM. It is synchronous unlike useEffect.

React Hooks in-depth > useLayoutEffect - CodeSandbox

useLayoutEffect Example

If you use server rendering, keep in mind that neither useLayoutEffect nor useEffectcan 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).

useImperativeHandle

Most likely to never use this one; probably used only by library authors.

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.

React has top-down data pass but useImperativeHandleflips the model on its head — you can actually have a child control the parent.

We’re going to use it in conjunction with another feature called forwardRef that again, you probably won't use but libraries will use on your behalf. Let's explain first what it does using the example and then we'll explain the moving parts.

React Hooks in-depth > useImperativeHandle - CodeSandbox

useImperativeHandle

In the example above, whenever you have an invalid form, it will immediately focus the first field that’s invalid.

If you look at the code, ElaborateInput is a child element so the parent component shouldn't have any access to the input contained inside the component. Those components are black boxes to their parents. All they can do is pass in props. So how do we accomplish it then?

The first thing we use is useImperativeHandle. This allows us to customize methods on an object that is made available to the parents via the useRef API.

Inside ElaborateInput we have two refs: one that is the one that will be provided by the parent, forwarded through by wrapping the ElaborateInput component in a forwardRef call which will ten provide that second ref parameter in the function call, and then the inputRef which is being used to directly access the DOM so we can call focus on the DOM node directly.

From the parent, we assign via useRef a ref to each of the ElaborateInputs which is then forwarded on each on via the forwardRef. Now, on these refs inside the parent component we have those methods that we made inside the child so we can call them when we need to. In this case, we are calling the focus when the parent knows that the child has an error.

Again, you probably use this directly but it’s good to know it exists. Normally it’s better to not use this hook and try to accomplish the same thing via props but sometimes it may be useful to break this one out.

Folks! thanks for staying with me so far. Hopefully, this gets you on good grounding with React Hooks.

Hi 👋, I’m Saidur Rahman — a Software Engineer at Atlassian Growth Team.

Connect with me on Twitter or LinkedIn to chat about software engineering and growth hacking.

Thanks for taking the time to check out my article! If you liked this article, drop a few claps and recommend this article to your friends. I’d love to hear your thoughts.

Learn React - Best React Tutorials (2019) | gitconnected


React Hooks in 20 Minutes was originally published in Level Up Your Code on Medium, where people are continuing the conversation by highlighting and responding to this story.