Steven Ly's Developer Portfolio

Five main benefits of using React Hooks

Steven Ly's profile imageSteven Ly
Five main benefits of using React Hooks's cover image

The React Class approach has long been the only way to create a state and lifecycle-managed component. However, as practice has shown, the approach has several disadvantages. The React team added a new approach to the existing one to fix the shortcomings of the old one and also added new capabilities for managing the state.

In this blog, I am going to share our experience of using React Hooks and compare the new approach with React Class. This article describes only the most significant changes and will allow you to learn more about the approaches for both beginners and experienced developers.

What are React Hooks

Starting with version 16.8, React has a new approach to describing the lifecycle and state of a component. According to the documentation, React Hooks brings several improvements to efficiently writing components, and also allows you to solve some of the difficulties in the applied patterns. While React Hooks are a new approach to writing components, they are only recommended, not required. According to the React team, there are no plans to remove the classes, and both approaches can be used in the same project (however, a component should only use one of the approaches).


Less Repetitive Code

React provides a lot of possibilities for sharing code. A certain computation can be moved into a shared function and reused by other components. The component itself is shared code and can be easily reused in the application. However, if you need to reuse only the behaviour or state of a component, then in the case of a React Class component, this is a difficult task in implementation.

Custom React Hooks allow you to combine coherent code that is directly responsible for the state or lifecycle of a component and make it shared between components.

// Shared Hook
const useClicker = () => {
  // Set state
  const [counter, setCounter] = useState(0)

  // Define function
  const increment = useCallback(() => {
    setCounter(counter + 1)
  }, [counter])

  // Add effect
  useEffect(() => {
    setCounter(1)
  }, [])

  return [counter, increment]
}

const Button = () => {
  // Using shared hook
  const [counter, increment] = useClicker()
  return <button onClick={increment}>Clicks: {counter}</button>
}

const Submit = () => {
  // Using shared hook
  const [counter, increment] = useClicker()
  return <input type="submit" onClick={increment} value={`Clicks: ${counter}`} />
}


Less Definition Code

The ability to write a component without using a class existed in earlier versions, but such functional components could not describe their lifecycle and state. At the moment when the component needed to have a state, the component needed to be wrapped in a class, declare `constructor` and transfer tags to the `render` method.

In addition to requiring a functional approach to a component (which reduces the number of lines of code to declare a component), React Hooks also allows for code to be reused, which can significantly reduce the number of lines of code.

const Button = () => {
  // Commented out code shows the similar code
  // written using React Class

  // constructor() {
  // super()
  // this.state = { counter: 0 }
  // }
  // setCounter(counter) {
  // this.setState({ counter })
  // }
  const [counter, setCounter] = useState(0)

  // componentDidMount() {
  // this.setCounter(1)
  // }
  useEffect(() => setCounter(1), [])

  // render() {
  // const { counter } = this.state
  // return  this.setCounter(counter + 1)}>Clicks: {counter}
  // }
  return <button onClick={() => setCounter(counter + 1)}>Clicks: {counter}</button>
}


Less Lifecycle Code

The React Class approach allows you to control a component at various lifecycle stages of a component using the `componentDid *` and `componentWill *` methods. However, as the application develops, the component may become more complex, and it will be necessary to extend the lifecycle methods to carry out additional logic. As a result, lifecycle methods become cumbersome, additionally, the code in these methods is difficult to understand as it is cut off from the rest of the code that can manage state.

`useEffect` allows you to control `componentDidMount`, `componentDidUpdate` and `componetWillUnmount` in a single method, and with the use of a custom hook, this logic can be combined with state management to improve the readability and understanding of the code.

// This example shows compounding useEffect methods in component

// First hook contains useEffect to set the letter
const useLetter = () => {
  const [letter, setLetter] = useState(null)

  useEffect(() => {
    setLetter('A')
  }, [])
  return letter
}

// Second hook contains useEffect to set the digit
const useDigit = () => {
  const [digit, setDigit] = useState(null)

  useEffect(() => {
    setDigit('1')
  }, [])
  return digit
}

// useLetter & useDigit hooks applied
const Button = () => {
  const letter = useLetter()
  const digit = useDigit()

  return <div>{letter} : {digit}</div>
}


Less HOC Wrappers

One of the patterns for shared code is High Order Components. There are a significant number of plugins, for example, recompose, which allow you to quickly expand the capabilities of a component and customize its behaviour. However, wrapping components results in multi-wrapped components as well as a deeply nested DOM tree of elements, which can have a performance impact on component rendering.

React Hooks allows you to take logic out of the High Order Component and reuse it in the component itself. With this approach, the logic will be injected into the component without wrapping the component and without creating deeply nested nodes.

// Shared Hook
const useLetter = () => {
  const [letter, setLetter] = useState(null)

  useEffect(() => {
    setLetter('A')
  }, [])
  return letter
}

// Customized hook that uses shared hook
const useLetterAndDigit = () => {
  const letter = useLetter()
  const [digit, setDigit] = useState(null)

  useEffect(() => {
    setDigit('1')
  }, [])
  return [letter, digit]
}

// Custom hook applied
const Button = () => {
  const [letter, digit] = useLetterAndDigit()
  return <div>{letter} : {digit}</div>
}


Fewer Context Problems

The behaviour of `this` in Javascript is already complicated. In React Class, for a React beginner, context can be an additional challenge in understanding how a component works. For example, a component's React Class methods usually have `.bind (this)` to set the context. Such code, unfortunately, increases the codebase and can also cause an error if not set.

React Hooks do not require the use of this context, only certain variables are used both inside the hooks and in the template.

const Button = () => {
  // Commented out code shows the similar code
  // written using React Class

  // constructor() {
  // super()
  // this.state = { counter: 0 }
  // this.setCounter = this.setCounter.bind(this) // <===== sets context
  // }
  // setCounter() {
  // this.setState({ counter: this.state.counter + 1 })
  // }
  const [counter, setCounter] = useState(0)

  // render() {
  // const { counter } = this.state
  // return Clicks: {counter}
  // }
  return <button onClick={() => setCounter(counter + 1)}>Clicks: {counter}</button>
}


Wrap Up

Undoubtedly, React Hooks solved not only the above problems but also brought several other benefits, for example, in optimizing minified code. However, React Hooks have several restrictions and rules that must be followed. Failure to follow them can also lead to a complication of the codebase, the creation of hard-to-catch bugs, or poor performance.

This article compared React Class and React Hooks approaches based on my own experience. For a deeper comparison, I recommended referring to the official React Hooks documentation.