When I first learned to code in React, I was taught to build class components to manage state. I was told functional components were dubbed “dumb components” as they were relegated to only the most basic functionality that didn’t require state. Since then, the React devs have introduced hooks as a way to recapture the benefits of classes within functional components.
To the unpracticed, the difference between classes and hooks might not seem obvious or important, and if you’re already very comfortable with class components, you might be reluctant to switch design styles; however, hooks were designed to be an improvement over class components by addressing some of their key issues. In short, classes tend toward unintuitive design that tends to get worse as apps grow in scale. Hooks were designed to be simple to use and understand, and not require peculiar app structure to function properly.
I’ll go over how to use Reacts core hooks: useState, useEffect, and useContext; and how to convert a simple class based component into one that uses hooks instead, so that you can get started with these tools yourselves.
The first hook I’ll go over is useState. Aptly named, useState allows functional components to initialize, access, and adjust a state property. This is done by calling useState and passing an initial value for the state variable you want to manage. Let’s go over this by seeing how this would be done in a class and then rewriting it with the hook.
Here we have a class component that uses state to keep track of how many times a button has been pressed and renders that value onto the button. Each time the button is pressed it triggers an event that uses setState to update the value of timesPressed. This structure should be very familiar to you if you’ve been using classes. Now lets hook this component up instead.
First, be sure to import useState from React. Then call useState, passing in an initial value of 0, and deconstruct the return to the variables timesPressed and setTimesPressed. When called, useState returns an array whose first value returns the current state (which is initialized by the argument passed to useState) and second value returns a function to update that state. Now we can use timesPressed in place of this.state.timesPressed to display state, and we can place setTimesPressed inside our event instead of this.setState. You can call useState as many times as needed to add different state values.
Next, let’s take a look at how useEffect works. This hook serves as an elegant way to access the functionality of lifecycle methods like componentDidMount, componentDidUpdate etc. As before, useEffect needs to be imported from React and called at the top level of your function.
useEffect accepts two arguments: a function to call and optionally an array of dependencies. Without a second argument, useEffect will run on each render. With our current code, useEffect will log the message to our console on first render and every time our button is pressed as well. If we add an empty array as our second argument, then useEffect will only run with the initial render. In this way, an empty array allows useEffect to function like componentDidMount, and thus is a favorite mode for things like fetch calls.
Additionally, you can pass dependencies into the array to specify and optimize when useEffect runs. Then useEffect will only run when its dependencies change. This mode lets functional components replicate componentsDidUpdate. useEffect can be called multiple times to allow for different effects with different dependencies.
If you include state values or props within a useEffect function, React will display a warning if that values are not also listed as dependencies.
Lastly, the useContext allows you to easily harness the power of the Context API and shared state. Like the the original API, useContext requires that you create a context object and wrap your app in a provider component that will contain the state to be shared across components. Unlike the API, you do not need to wrap components in consumer tags to grant them access to shared state. Instead, you simple call the useContext hook, passing in the context as an object. Then you can destructure the return of useContext according to which pieces of state each component needs access to.
That’s all the hooks I’m covering in this post, and these three offer quite a lot of functionality to components; however, there are additional hooks to learn about that account for peculiar situations and grant more nuanced control over your components. So, once you become familiar with the basic hooks, it’s worth looking into those as well. Now, you’re all hooked up. Happy coding!