Hooked on React

How React’s new Hooks API can help you win the class war

This article assumes you are already reasonably familiar with React.

What are hooks?

Hooks are JavaScript functions that let you interact with React components’ state and lifecycle features from plain function components, rather than having to write a class, or wrap components in higher-order components.

Hooks were introduced in React 16.8 and are now a stable feature. Hooks also work in modern versions of React Native.

Watch Dan Abramov’s introduction to React Hooks at React Conf 2018.

React provides a number of built-in hooks, and gives you a simple set of rules for writing your own hooks.

The useState hook

The useState hook is probably the hook you’ll use the most. It gives you an easy and clean way to access a component’s internal state without needing to declare the component as a subclass of React.Component .

An example

The old way:

Note the use of pluralise is optional but I hate it when a UI says ‘1 times’.

import React from 'react' class Counter extends React.Component {

constructor(props) {

super(props)

this.state = {

count: 0

}

} increment = () => {

this.setState({ count: this.state.count + 1 })

} render() {

const { count } = this.state

return (

<div>

<p>You clicked {pluralise(count, 'time')}</p>

<button onClick={this.increment}>Increment</button>

</div>

)

}

}

Now using hooks:

import React, { useState } from 'react' const Counter = () => {

const [count, setCount] = useState(0)



const increment = () => setCount(count + 1)



return (

<div>

<p>You clicked {pluralise(count, 'time')}</p>

<button onClick={increment}>Increment</button>

</div>

)

}

What’s going on here?

const [count, setCount] = useState(0)

The useState hook returns two things, the piece of state you care about, and a function to set the value of that state. In the example above we care about a piece of state called count , which we have given a default value of 0 , and we want to be able to set the value of that count . The value and the function are returned in a two-element array, so we use array destructuring as a shorthand to assign those values.

The useEffect hook

The useEffect hook lets you perform side effects in function components. You can think of the useEffect hook as componentDidMount , componentDidUpdate , and componentWillUnmount combined.

Example

Let’s say we want to update the page title whenever the count is incremented.

The old way would be:

import React, { Component, Fragment } from 'react' class Counter extends Component {

constructor(props) {

super(props)

this.state = {

count: 0

}

} setTitle = count => {

document.title = `You clicked ${pluralise(count, 'time')}`

} increment = () => {

this.setState({ count: this.state.count + 1 })

} componentDidMount() {

this.setTitle(this.state.count)

} componentDidUpdate() {

this.setTitle(this.state.count)

} render() {

const { count } = this.state

return (

<Fragment>

<p>You clicked {pluralise(count, 'time')}</p>

<button onClick={this.increment}>Increment</button>

</Fragment>

)

}

}

But using hooks:

import React, { Fragment, useState, useEffect } from 'react' const Counter = () => {

const [count, setCount] = useState(0)



const increment = () => setCount(count + 1)



useEffect(() => {

document.title = `You clicked ${pluralise(count, 'time')}`

}) return (

<Fragment>

<p>You clicked {pluralise(count, 'time')}</p>

<button onClick={increment}>Increment</button>

</Fragment>

)

}

Using the useEffect hook tells React that the component needs to do something after render. React will remember the function you passed to useEffect and call it later after performing the DOM updates.

Note: the function passed to useEffect is created anew on every render. This ensures that count never gets stale. Every time the component is re-rendered, a different effect is scheduled, replacing the previous one.

How to prevent effects from triggering when there is no change

The useEffect hook takes a second paramater that it uses to compare with its current state. The example above will rewrite document.title even if the count has not changed. To fix this we call useEffect but also pass in the count .

useEffect(

() => {

document.title = `You clicked ${pluralise(count, 'time')}`

},

[count]

)

Now if the count is unchanged then the effect function will not be invoked.

Tip: You can force an effect to only run once by passing in [] as the second argument, thus simulating the componentDidMount lifecycle.

Non-blocking vs blocking effects

The majority of effects don’t need to happen synchronously and, unlike componentDidMount or componentDidUpdate , effects scheduled with useEffect don’t block the browser from updating the screen. In the rare cases where you need to ensure that an effect is applied after the component has finished rendering, there is a separate useLayoutEffect hook with an identical API to useEffect .

Cleaning up after an effect.

The effect function supplied to useEffect can return an optional cleanup function which gets executed when the component unmounts. React also cleans up effects from the previous render before running the effects in the next render.

Other standard hooks supplied by React

useContext

The useContext hook accepts a context object (the value returned from React.createContext ) and returns the current value for that context . The current context value is determined by the value prop of the nearest <MyContext.Provider> above the calling component in the tree.

When the nearest <MyContext.Provider> above the component updates, this hook will trigger a re-render with the latest context value passed to that MyContext provider.

useReducer

An alternative to useState . The useReducer hook accepts a Redux style reducer of type

(state, action) => newState

and returns the current state paired with a dispatch method. Think of it as like a mini Redux that’s specific to a component.

useCallback

Pass a callback and an array of dependencies. useCallback will return a memoised version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimised child components that rely on reference equality to prevent unnecessary renders.

See also: How to Memoize Components in React.

useMemo

Pass a create function and an array of dependencies. useMemo will only recompute the memoised value when one of the dependencies has changed, avoiding recalculations on every render.

useRef

Returns a mutable ref object whose .current property is initialised to the passed argument (initialValue) . The returned object will persist for the full lifetime of the component.

If you pass a ref object to React with <div ref={myRef} /> , React will set the ref.current property to the corresponding DOM node whenever that node changes.

Read more on the use of the ref attribute in the React docs.

Tip: You can use, useRef for more than the ref attribute. It’s useful for keeping any mutable value around, similar to how you’d use instance fields in classes.

useLayoutEffect

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.

Tip: Use the standard useEffect whenever possible to avoid blocking visual updates.

useDebugValue

Used to display a label for custom hooks in React DevTools.

Do hooks replace Redux?

No. Redux is still an excellent framework for managing shared or application-wide state. React Redux has supported the Hooks API since v7.1.0 and exposes hooks such as useDispatch and useSelector that enable you to subscribe to the Redux store and dispatch actions, without having to wrap your components in connect .

The useSelector hook is roughly equivalent to the mapStateToProps argument to connect . The selector will be called with the entire Redux store state as its only argument. It is run whenever the component renders. useSelector will also subscribe to the Redux store, and run your selector whenever an action is dispatched.

Example

We want a number of counter components to all share the same count. Define a simple Redux count state , reducer , and store like:

import { createStore, combineReducers } from 'redux' // count starts at 0

const INITIAL_COUNT = 0 // the count state reducer

const count = (state = INITIAL_COUNT, action = {}) => {

switch(action.type) {

case 'increment-count': return state + 1

default: return state

}

} // export the redux store

export default createStore(combineReducers({ count }))

A Count component just gets the count from the state using useSelector and displays it in a span :

import React from 'react'

import { useSelector } from 'react-redux' const Count = () => {

const count = useSelector(({ count }) => count)

return <span>{count}</span>

}

The useDispatch hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions as needed.

Example

Using the Count component from above.

import React, { Fragment } from 'react'

import { useDispatch } from 'react-redux'

import Count from './Count' const Counter = () => {

const dispatch = useDispatch()

const increment = () => dispatch({ type: 'increment-count' }) return (

<Fragment>

<Count />

<button onClick={increment}>Increment</button>

</Fragment>

)

}

When passing a callback using dispatch to a child component, it is recommended to memoise it with useCallback , otherwise child components may render unnecessarily due to the changed reference.

import React, { Fragment, memo, useCallback } from 'react'

import { useDispatch } from 'react-redux'

import Count from './Count' const Incrementer = memo(({ onClick }) => (

<button onClick={onClick}>Increment</button>

)) const Counter = ({ count }) => {

const dispatch = useDispatch()

const increment = useCallback(

() => dispatch({ type: 'increment-count' }),

[dispatch]

) return (

<Fragment>

<Count />

<Incrementer onClick={increment} />

</Fragment>

)

}

What about React Router?

React Router has supported the Hooks API since v5.1.

useHistory

import { useHistory } from 'react-router-dom' export const HomeButton = () => {

const history = useHistory() const goHome = () => history.push('/home') return (

<button type="button" onClick={goHome}>Home</button>

)

}

Other hooks exposed by React Router:

useLocation

useParams

useRouteMatch

What about testing?

Write standard unit tests for your custom hooks; hooks are just functions.

From React’s point of view, a component using hooks is a regular component. You can test it as you usually would, and, if your hook does something complex, you can test the hook independently and mock it out from the component’s test, just like you would with any other dependency.

See reactjs.org/docs/testing-recipes.html for more.

The ‘Rules of Hooks’

In order for hooks to work they need to follow three simple rules.

Firstly: Your hook must start with the word use .

Secondly: Only call hooks from React functions, or from other hooks.

Don’t call hooks from regular JavaScript functions.

Note: You can’t use hooks inside of a class component, but you can mix classes and function components with hooks in a single tree while you migrate your app from old style React.Component classes. In the longer term, the React team expects that plain functions with hooks to be the primary way people write React components.

Thirdly: Only call hooks at the top level of your function.

Don’t call hooks from inside loops, conditions, or nested functions or you run the risk that your hooks won’t be called in the same order each time a component renders.

There is an ESLint plugin called eslint-plugin-react-hooks that enforces these rules.

Note: If you’re using Create React App , then you’ll see that eslint-plugin-react-hooks is included in the latest release of react-scripts already.

Custom hooks — writing your own hooks

Share logic between two JavaScript functions by extracting it to a hook; a JavaScript function whose name starts with use that can call other hooks.

Your custom hook doesn’t need to have a specific signature. You can decide what it takes as arguments, and what, if anything, it returns. It’s just like a normal function. Its name must start with use so that you can tell at a glance that the Rules of Hooks apply to it.

Example

Let’s assume that any self-respecting counter needs to show a current count , be able to increment that count , and update the page title with the current count when it changes. (This of course assumes there is only one count per page). We can write a useCount hook thusly:

export const useCount = () => {

const [count, setCount] = useState(0)

const increment = () => setCount(count + 1) useEffect(() => {

document.title = `You clicked ${pluralise(count, 'time')}`

}, [count]) return [count, increment]

}

Then the Counter component could be written as:

const Counter = () => {

const [count, increment] = useCount() return (

<Fragment>

<p>You clicked ${pluralise(count, 'time')}</p>

<button onClick={increment}>Increment</button>

</Fragment>

)

}

Every time you use a custom hook, all state and effects inside of it are fully isolated. In this example, each component using the useCount hook will have its own count state, starting at 0 . If you wish to share state between components you can use Redux, or use one of the many global state management hooks (for example useGlobalHook .)

Custom hooks enable encapsulation and sharing of logic in ways not possible in React components before. They can cover a wide range of use cases such as form handling, animation, API calls and subscriptions, timers, and more.

There are already thousands of interesting React hooks out there in the wild that you can just use.

Winning the class war

Experienced JavaScript developers know that use of classes is problematic at best and treat the presence of the this keyword as a code-smell. JavaScript is not a real Object-Oriented language, and needlessly attempting to use it as one can be a source of hard-to-find and subtle bugs.

Hooks allow you to skirt these dangers by simplifying your components down to what they actually do, rather than forcing the components to embrace the mysteries of React.Component (or React.PureComponent ), or get tangled up in deep trees of composed higher-order components.

In conclusion

Use hooks. Future you will love you.

Future Me Hates Me by The Beths (offical music video)

Links

Links shown in the same order they appear in the article.