Getting started with React hooks

Hooks are new APIs added in React 16.7, it allows you you to write statefull components and use component life cycles without writing class components. In this blog post, I explain how to use Hooks.

What are React hooks?

React team has always preferred functional components over class components, but it was not possible to write statefull components and use component life cycle without writing class components. Hooks enables you to write statefull components and use life cycle methods without writing a class component.

Why not to write class components?

Class components have the following disadvantages.

  • Class components make it difficult for React team to optimise components for performance.
  • They are difficult to test compared to functional components.
  • Binding this is confusing for developers without solid understanding of javascript.
  • Their are lots of life cycle methods and they are confusing to understand and defferentiate for beginners.

Builtin Hooks

The following built-in hooks are available:

  • useState
  • useEffect
  • useContext
  • useReducer
  • useRef
  • useCallback
  • useMemo
  • useImperativeMethods
  • useMutationEffect
  • useLayoutEffect

The useState hook

With useState you can have state in a functional components.

Lets say you have a class components which tracks the number of times a button is clilcked.

import React from "react";
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.updateCounter = this.updateCounter.bind(this);
    this.state = {
      counter: 0
    };
  }

  updateCounter() {
    this.setState({ counter: this.state.counter + 1 });
  }
  render() {
    return (
      <div>
        <button onClick={this.updateCounter}>
          Click count {this.state.counter}
        </button>
      </div>
    );
  }
}
export default Counter;

And you want to refactor it to a functional component with hooks.

import { useState } from "react";
import React from "react";

function Counter() {

  const countState = useState(0);
  const count = countState[0];
  const setCount = countState[1];

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Click count {count}</button>
    </div>
  );
}

export default Counter;

On line 1 we imported useState. On line 5 we called useState hook with an initial state of 0.

useState returns an array with two values. The first value is the current state (this.state.counter in class components) and the second value is a function for updating state (this.setState({}) in class components).

You can simplify the lines 6 to 8 using ES6 array destructuring.

import { useState } from "react";
import React from "react";

function Counter() {

  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Click count {count}</button>
    </div>
  );
}

export default Counter;

The useEffect hook

The useEffect hook is used for performing side effects from function components (side effects are things you do outside a react components. For example network requests, manipulating page header, saving something in localStorage and so on).

It’s equivalent to componentDidMount and componentDidUpdate in class components.

In the following example we use useEffect hook to update page title

import React from "react";

import { useEffect } from "react";

function TitleUpdater() {
  let updatePageTitle = () => {
    document.title = `component mounted or updated`;
  };
  useEffect(updatePageTitle);
  return <div>use effect hook</div>;
}
export default TitleUpdater;

We import useEffect from react and call use useEffect anywhere in component with a callback. The callback is executed when component get mounted or updated.

You may need to undo side effects when component is unmounted, for example you may want to revert page title when the component gets unmounted. In class components you use componentWillUnmount, in useEffect hook you return a function from callback. The returned function is executed when the components is about to get unmounted.

In the following example we update page title when component is unmounted.

import React from "react";

import { useEffect } from "react";

function TitleUpdater() {
  let updatePageTitle = () => {
    document.title = `component mounted or updated`;
    return () => {
      document.title = `unmounted`;
    };
  };
  useEffect(updatePageTitle);
  return <div>use effect hook</div>;
}
export default TitleUpdater;

The useContext hook

useContext hook provides a handy way to access Context from within child component.

Instead of writing

import React from "react";

export const MyComponent = () => {
    return <MyContext.Consumer>
            {value => <div>{value}<div/>}
           </MyContext.Consumer>
};

you can simply write

import React, {useContext} from "react";
import {myContext} from "../state/my-provider";

export const MyComponent = () => {
    const value = useContext(myContext);
    return <div>{value}<div/>
};

The useReducer hook

The useState hook is great for very simple single property state objects. useReducer hook allows you to have complex states with multiple property. It is similar to the redux reducers, if you are already familiar with redux.

Its syntax is:

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

it takes two parameter, a reducer function and the initial state and returns the state and a dispatch method to update the state.

The reducer function looks like this

function reducer(state, action) {
        switch (action.type) {
            case 'toggle':
                return {...state, isOpen: !state.isOpen,};
            case 'open':
                return {...state, isOpen: true,};
            case 'close':
                return {...state, isOpen: false,};
            default:
                throw new Error();
        }
    }

The initial state can be any object

const initialState = {
        isOpen: false,
        ...lotsOfOtherProperties
    };

You can dispatch updates using dispatch method.

dispatch({
            type: 'toggle',
        });

Dispatch methods may have payload as well.

dispatch({
           type: 'add',
           payload: aValue,
       });

You can access the payload in your reducer function in the action parameter (as shown above).

Hooks best practices

While working with hooks consider the following best practices:

  • Avoid multiple side effects in a single useEffect call
  • Do not call hooks from within if conditions or loops
  • Use hooks only within functional components and not in your own Javscript code.

Further reading

To see how to work with other builtin hooks or write your own hook, have a look at Hooks API