Share

engineering

7min read

React Hooks vs. Wrapper Hell—Writing State in a Function with Ease

React Hooks vs. Wrapper Hell—Writing State in a Function with Ease

Introduction to React Hooks

Web development is changing at a rapid pace. Especially in React development, it is important to keep yourself up to date with new features coming out, because understanding the concept of each of them will help you choose the right tool for your project. Today, we will learn a new feature released by the React team. Keep scrolling down!

As a React developer, you’ve probably heard the term “React Hooks” somewhere before. If not, don’t worry, you will have a good understanding of it at the end of this blogpost.

Before I answer the question of what are React Hooks, let me show you why they came to be.

Have you or any member of your team ever met one of these situations while developing your React applications?

  • “Wrapper Hell”: there are two popular types of sharing logic in components which are Higher-Order Components and Rendering Props. When your applications have a massive quantity of nested Components, they will cause a “Wrapper Hell.”
  • Giant Components: when your app reaches a complex logic, a single Component can be up to a thousand lines of code.
  • Confusing Classes: your React application crashes, and you realize that you forgot to bind this. (oops!).
This is how “Wrapper Hell” looks like.

It’s time for React Hooks to appear and save the day. So, what are React Hooks?

React Hooks are a newly released feature, introduced in React version 16.8. They allow you to use State and life-cycle methods without using a Class.

Now, let’s start exploring core React Hooks APIs.

Handling internal State

The appearance of State is to make a Component to be more interactive and dynamic. State can be defined as an object that stores dynamic data. Before React Hooks release, State was only used in a Class Component. To modify values of State, in each Component, React provides a method which is called setState.

As an example, we are going to use a basic React app, counting the times you click on a button.

Below is the standard way to handle State in a Class Component:

import React from 'react';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      count: 0,
    };
  }

  setCount = () => {
    this.setState(prevState => ({
      count: prevState.count + 1,
    }));
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.setCount}>
          Click me
        </button>
      </div>
    );
  }
}

export default App;

And this is how we implement React Hooks:

import React, { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>
        Click me
      </button>
    </div>
  );
}

export default App;

The very first thing that you can see in the above example is that useState() allows you to declare State in a function component in a shorter way. Now, let’s analyze each line of code.

import React, { useState } from ‘react’;

Before diving into more details, remember to import useState (same with useEffect which I will mention later), and use React version above 16.8 for your application. Otherwise, they won’t work.

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

useState() returns an array in which array[0] is a value of State and array[1] is a corresponding function to setState() in Class Component. The very first argument of useState() is defined as a default value. In the line above, 0 here is passed into useState() which is an initial value of State, and it is only used in the first render.

In a function component, useState() does not stop at an argument 0, but it can be called several times using array destructuring syntax like below:

const [count, setCount] = useState(1)
const [dog, setDog] = useState('Corgi');
const [book, setBooks] = useState([{ title: 'React Hooks', review: ‘Awesome’ }]);

One more thing that differentiates React Hooks from Class Component:

<p>You clicked {count} times</p>

Finally, we can get the value of count and forget about this.

Handling life-cycle methods

When we need to fetch APIs, declare eventListener, or alter DOM, we usually use Component life-cycle methods. Here I will add componentDidMount() which is invoked immediately after Class Component is mounted, and componentDidUpdate() to get re-rendered when changes occur.

Below is how a Class Component looks like:

import React from 'react';

class App extends React.Component {
  constructor(props){
    super(props);

    this.state = {
      count: 0,
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  setTitle = (e) => {
    this.setState(prevState => ({
      count: prevState.count + 1,
    }));
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.setTitle}>
          Click me
        </button>
      </div>
    );
  }
}

export default App;

However, magic happens if we implement the same feature in React Hooks:

import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => 
        setCount(prevCount => prevCount + 1)}
      >
        Click me
      </button>
    </div>
  );
}

export default App;

A walk in the park, right?

useEffect() in the above example is executed after the first render and also when the value of count changes. If you are familiar with React life-cycle methods, you will see that useEffect() is quite similar to componentDidMount(), componentDidUpdate(), and componentWillUnmount(). However, instead of writing logic code in various life-cycle methods, you can do it in one manageable place. Also, in case you would like to separate logic codes, useEffect() can be called multiple times.

Now, notice this:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);

useEffect() takes its first parameter as a callback and the component will be re-rendered each time when the second parameter count changes. So, what if you only want it rendering one time after the first render? You got the answer, right? Here, we only need to pass an empty array [ ] as the second parameter.

Sometimes, to avoid stepping on a memory leak, you will need useEffect() cleanup feature by returning a function inside of the effect:

useEffect(() => {
  console.log(“Subscribe to Something”);
  return function cleanup() {
    console.log(“Unsubscribe to Something”);
  };
});

In every render, the effect above will run more than one time. React Hooks help to clean up the previous render’s effect before running the next one.

Building your custom hook

To extract your reusable logic, React Hooks allow you to build your own custom hook in a separate file. Awesome!

Take a look at the example below:

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

function useScroll() {
  const [scrollTop, setScrollTop] = useState(0);

  function handleScroll(e) {
    setScrollTop(e.srcElement.scrollingElement.scrollTop);
  }

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);

    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  });

  return scrollTop;
}

function Example() {
  const scrollTop = useScroll();

  return (
    <div style={{ height:4000px” }}>
      <h1 style={{  position: “fixed” }}>{`scrollTop is: ${scrollTop}`}</h1>
    </div>
  );
}

What you see right now is just applying useState() and useEffect(). I explained both of them in the last sections, in useScroll() function. So, whenever you need to use the same feature as useScroll(), you need to import it in your file and use it, instead of writing every line of code again.

Here, useScroll() is called with a simple line of code:

const scrollTop = useScroll();

Wrapping up

Thanks to React Hooks, we do not need to use Class every time to manage State. However, the React team does not have any plans to remove Class Components, at least for now. Thus, we do not need to rewrite all of our existing Class Components to Hooks. (A hard work anyway!)

Great, you have a grasp of React Hooks use-cases, as well as how to implement them to your React applications. If you want to dive deep into React Hooks APIs, there are more, such as useContext, useReducer, useCallback. Check them out here. If you want a helping-hand with building User Interfaces, don’t hesitate to contact us.

-

Share

Nam

Junior Software Engineer

Did you enjoy the read?

If you have any questions, don’t hesitate to ask!

Did you enjoy the read?

If you have any questions, don’t hesitate to ask!