React Hooks Are Great

Insight

Natalia Calvo

Engineer

At DEV we use the React library to build the front-end, or user-facing, side of our web apps. It is a JavaScript library maintained by Facebook and any DEV engineer will tell you, completely objectively, that it's the most popular front-end framework. When I was first learning React in DEV’s winter bootcamp this year one of the most important things we talked about was React Hooks, which was part of a major update to React. Like a true rookie React developer I took the ease of use for granted and had no concept of how much easier they made my life. It wasn’t until I looked further into React Hooks and what they replaced that I realized how much simpler and more accessible the update made programming in React. They allow us to use quintessential React features without writing class-based components, which has inspired many a meme about the very specific frustrations it creates. In this blog post I will attempt to (succinctly) explain why React hooks are such a game-changer in React development by understanding what life was like before them.

Part 1: Passing down some Context

Before React v 16.8, which was released early 2019, developers had to write class-based components. If you don’t know what the phrase means, don’t worry, I didn’t either. By replacing classes React made its framework more accessible to new programmers who might not have had exposure to classes before. To break this phrase down I’ll start with the word “components.” Components are what make React, React. React docs define them concisely: “Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.” Essentially, a component is a piece of a website, which can be as small as a header or input box, or encompass multiple components, like a navbar or login form, or an entire page. The “class-based” part of class-based components is a little more difficult to understand. As the name implies, it uses classes, which can be thought of as templates for reusable objects. They include different methods, which are just functions. When using class-based components we have to extend the React.Component class, which just means it inherits all the default functionality from React while allowing us to define other methods. Let’s look at an example:

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

The first method we call is the constructor, which creates a new instance, in the form of an object, of the class Counter. An object, for reference, is just a data structure made up of key-value pairs. The constructor also initializes the state, which is an object that holds values that persist through re-renders of elements, and gives it an initial value. We can update the state at any time with the setState function. It also binds any methods defined outside the constructor to the instance so they can be referenced later. One of these methods is incrementCount which just adds one to the state. We also define a render method, which every React component must have. It describes exactly what elements should be rendered onto the page.

Now that we have some understanding of class components, let’s talk about what makes them so difficult to use. There are 3 big problems that they cause. Let’s start with the first one.

Part 2a: I Hate this

You may have noticed the “this” keyword, which is syntax that comes from JavaScript itself, not React, since we’re working with classes. There is a running joke in the React community that no one really knows what it means. This is of course an exaggeration, but it shows how hard it is to learn how to use it. “This” refers to the new object that is created by the constructor. That’s why we do this. incrementCount to access the method in the object. However, as the program becomes more complicated, so does figuring out how to use “this.” It’s also annoying to have to bind every single method and forgetting to do so can cause confusing errors. It is one of the most common things new React developers struggle with, especially if they didn’t have experience with classes before.

Part 2b: Compounded Components

In addition to the constructor, React class components also lets us access the components lifecycle methods. These are special methods that allow us to run side-effects, or functions that alter something outside of the component itself, like fetching data or changing the DOM. We can run these functions at each point in the component’s “lifecycle,” which includes when it is initially added to the DOM (mounting), when it renders, when it updates, and when it is removed from the DOM(unmounting). The DOM stands for Document Object Model and is an API that sets the structure of a document, or web page. These methods are useful, but they also require us to repeat logic between the methods if we want it to do the same things at each stage, leading to large components. It also groups together logic based on when it executes rather than grouping by related logic, which makes complex components hard to follow and harder to test.

Part 2c: Wrapper Hell 🔥

One of the biggest frustrations with React is that while it allows us to reuse components just fine, it doesn’t let us reuse logic easily. Let's say that in one component we want to render a list of a user’s followers, and in another we want to render a list of accounts a user is following. The logic in both of these components is essentially the same—what differs is only the source of the data. It would be really nice if we could extract that into a function and call it in each component. Unfortunately, this isn’t possible because React class components have to return a React element. Developers have found a way around this with Higher-Order Components and Render Props. Both of these cause Wrapper Hell.

I wasn’t joking about the memes. Let’s start with higher-order components. A higher-order component is a function that takes in a component, along with any other data it needs, and returns a new component with whatever data needed as a prop, or an argument that can be accessed by the component itself. If we use the same example above, this function would take in the component that renders all the data, along with any data or other arguments needed by the component (the list of followers or followed). The outer component would call all the methods that were essentially the same between the components (i.e. the lifecycle methods, which set the state and handle mounting/updates/unmounting, and other methods if necessary) and return the component with the updated data as a prop. This way we don’t have to write the same exact methods twice.

Render props are similar. It is a component that takes a function as, you guessed it, a render prop. A “render prop” is just the name for a function that’s passed into a component to be rendered by that component.  The function itself returns the actual component we want to render. Going back to our example, the function given by the render prop will return either the component that renders a list of followers or a list of accounts followed, and of course we can pass in the necessary data as a prop to the component as well.

Part 3: React Hooks to the Rescue

React Hooks solve all of these problems because components, instead of being classes, are now functions. This means that not only do we not have to deal with class syntax (bye “this”!) but we can extract logic into functions and then reference them later easily by just calling the function inside of our functional component. So, what are hooks?

Hooks are functions that allow us to use state and lifecycle methods without actually dealing with them directly as we did in classes. There are many types of hooks React provides out of the box, and we have the ability to make a custom hook for more complicated functions, but for now I’ll talk about useState and useEffect. useState is exactly what it sounds like—it allows us to initialize a state and returns a variable that stores the state and a function to update it. This gets rid of the confusing “this” keyword while allowing us to use state.

The other really powerful hook I will mention is the useEffect hook. This hook completely replaces the lifecycle methods in class-components. With lifecycle methods we had to write functions for each stage in the component’s lifecycle: whether it was mounting, updating, rendering, or unmounting. The problem with this was that we would often have to repeat code throughout the methods if we wanted to do the same thing at different stages. useEffect solves this by simply taking a function that runs after every render. This means it does the same thing whether it was the component’s first time rendering or if it was just updating. If the side effect we are trying to run needs a cleanup function, like unsubscribe from a data source or removing an event-listener, all we need to do is return that cleanup function. Part of the appeal of useEffect is that we can call it multiple times, allowing us to group related logic together instead of having it spread over lifecycle methods.

Part 4: Props for getting this far!

I hope that you’ve learned a little about React and its newest update. I’ve definitely acquired a greater appreciation for the React gods that have blessed us with this update. I admire Facebook and the React community for seeking to make React so much more accessible for first-time developers, and a lot more enjoyable too!

UP NEXT

Ways to Monetize

Paid, Free, or Somewhere in Between?

Insight

The Aesthetic-Usability Effect

Or: Why You Can Get Away With Anything If You're Hot

Insight

Content Management Systems

Insight