A Beginner’s Guide to React, Part 2: Hooks Edition

Betsy Sallee
10 min readJun 14, 2019

--

Source: https://pirates.fandom.com/wiki/James_Hook

If you’re a React engineer and you haven’t been living on the moon for the past nine months, chances are you’ve heard about React Hooks. The TL;DR is that before Hooks, class components were the only components that could track state and make requests to an API. Hooks are functions that enable us to use state and other React features in functional components. In this post, I’m going to walk you through four of the major Hooks — useState, useEffect, useContext, and useReducer — , all while refactoring the PhotoFinder app I built to go along with my 2018 post, “A Beginner’s Guide to React.” Let’s get started.

useState()

Before Hooks, the concept of state drew the clearest line in the sand between class components and functional components: class components had it, and functional components didn’t. Now, if you want your functional component to have state, you simply import the useState function from React and then…use it!

Here’s an example of a simple component that enables the user to select a region and a language:

Here it is in action:

Region and language picker with useState() hook

In this example component, we simply call useState and pass it a value (which can be of any data type) for the initial state. Since we have two pieces of state — region and language — , we call useState twice. Next, we use the array de-structuring syntax to pull off the value in state we want to create, as well as the setter function for this — and only this — specific piece of state. The naming convention for this setter function is always set{yourPieceOfState}. We are now able to simply use the variable representing each piece of state anywhere in our component, and when we want to change that value in state, we call the setter function and pass it a new value.

I mentioned before that useState can take any data type as an argument. This might seem strange at first, since we’re used to seeing state as an object. As such, it might seem more natural to pass an object to useState. This can be done, but there’s one important caveat to keep in mind. Before I explain, take a look at this refactored region and language selector component, which passes an object to useState:

The crucial takeaway from this refactor is on lines 6–9 and 15–18 — namely, when you pass an object to useState and then want to update only part of it, the old state will not be merged together with the new state in the way the setState method behaves in class components. As such, if we initialize state with an object and then want to update only part of it, we must manually merge state by spreading the values that aren’t changing into the setter function, along with the value that is changing. Ye be warned!

We’ll need one more Hook before we can start refactoring the PhotoFinder app from “A Beginner’s Guide to React,” so let’s move on.

useEffect()

In traditional (read: pre-Hooks) React, all data fetching and subscription management had to be handled within component lifecycle methods, which are only available to class components. But the useEffect Hook, which takes a function as an argument, enables us to interact with the outside world from within a functional component. I’m going to show you how it works, but first, let’s get up to speed in our refactor of the PhotoFinder app.

Do you remember how, in the initial version of our app, we held values for the user’s search term and the returned photos in state? When the app first loaded and the user hadn’t typed anything, we fetched photos inside componentDidMount that matched the search term coding and saved those photos to state. Then, whenever the user typed into the search bar, we fetched photos again — this time, matching the new term — and saved those photos to state. Let’s look at how we can get the same functionality with a combination of useState and useEffect.

First, we need to call the useState Hook two times — first for the term, and again for the array of photos. In order to match the hook-less version of our app, we’ll pass the string “coding” as the initial term state, and an empty array for the initial photos state.

Next, it’s time to set up our useEffect Hook. As I mentioned, useEffect takes a function as an argument. In this case, the body of that function will call fetchPhotos, which calls the Unsplash API and sets the resulting photos in state using the setPhotos setter method, which we got from our call to useState.

Note: I’m storing my API key in a .env file, which I’ve gitIgnored.

You may be wondering about the second argument we’ve passed to useEffect — namely, the array containing term. This second argument is important because useEffect, by default, is called after every render; the only way you can control when it is called is by passing it an array as a second argument. If that array is empty, useEffect will only be called twice: once when the component mounts and once when the component unmounts. But if the array isn’t empty — say, if it includes a value from stateuseEffect will only be called when that particular value changes. In our case, useEffect should only be called if the value of term in state changes, so we include term inside that array. If we fail to pass useEffect a second argument at all, we’ll get caught in an infinite loop — calling the API, setting state, an re-rendering, ad nauseam.

The useEffect Hook also enables us to set up — and clean up — event listeners. For instance, you might set up an event listener in the body of the function, and then clean it up in the function that useEffect optionally returns (useEffect returns a function or nothing). If it does, in fact, return a function, that function will be called right before the component unmounts, thereby acting as the “clean up” function.

The main takeaway here is that the useEffect Hook represents the union of componentDidMount, componentDidUpdate, and componentWillUnmount — but with greater flexibility and power. The traditional lifecycle methods forced us to smush unrelated tasks together (or break related tasks apart) based on when in the component’s lifecycle they should occur. But with useEffect, which, like useState, can be called multiple times, we’re able to group related functions, API calls, and values together, all while controlling mounting and unmounting behavior. Pretty cool, huh?

useContext()

If you’re a React developer, you’re probably all-too-familiar with the concept of props drilling — that is, the process of passing data down through a component tree, sometimes going several levels deep. You might find yourself in a deeply nested component when you realize you’re getting a value from props and you don’t know what it is or where it’s coming from. You’re then forced to embark on a mission to retrace your steps and figure out the source of the mysterious value. Sometimes, components in the middle of the tree don’t need a certain props value at all , but they have to receive it in order to pass it further down. It can get pretty treacherous.

The useContext Hook offers a new alternative, enabling us to share data anywhere in the component tree without having to manually pass it down via props — and without having to rely on the traditional React Context API, which can lead to deeply nested Consumers. As with the React Context API, you still need to call React.createContext() to initialize your piece of global state (i.e. context), and you still wrap your App in a Provider with the value pointing to a particular piece of global state. But instead of having to wrap the component that needs context in a Consumer, all you have to do is call useContext at the top of your component and deconstruct the value you need off of its return value.

If it’s confusing now, don’t worry — I’m going to give an example in a moment. But first, we need to go over the useReducer Hook.

useReducer()

The useReducer Hook goes hand-in-hand with useContext, as it enables you to manage global state (i.e. context) — without Redux — from inside any component. Let’s walk through the steps of using useContext and useReducer in tandem. I’m going to use these two Hooks to enable users to “favorite” particular photos in our PhotoFinder app, but please note: it’s best to only use context for pieces of state that your entire app depends on, such as a user or region data. In our case, it wouldn’t be very complicated to just hold the user’s favorite photos in state, especially because our components are not deeply nested (and in an ideal world, our app would have a back end, which would enable us to save the user’s favorite photos in a database). Please keep in mind that this example is intended simply as a teaching tool. That being said — let’s proceed.

First, Create a file called context.js, where you will call React.createContext to create your initial context object. Don’t forget to export it!

Then, create a file called reducer.js, where you will write a reducer function that takes state and action as arguments (think: Redux). This function will essentially be a giant switch statement, switching on the typeof action that is passed in. In our case, all we’re going to want to do is enable a user to favorite and unfavorite photos, which we will do with a single action type: TOGGLE_FAVORITE.

After we have our context and reducer files set up, we call useContext at the top level of our App component to create a piece of initial global state. On the next line, we call useReducer and pass, as the first argument, our reducer function, and as the second argument, the piece of initial global state created by useContext (again — don’t forget your imports!). Using array de-structuring syntax, we’re able to grab hold of global state and a dispatch function from the useReducer call. Our dispatch function is what’s going to enable us to talk to the reducer function we created in reducer.js.

Next, it’s time to wrap our entire App in your context Provider, passing both state and dispatch (which we got from useReducer) into the value prop in order to gain access to it in whatever component we call useContext. Our App component should look like this:

If we were holding favorites in state, rather than in context, we’d have to pass favorites into the PhotoList component, which would in turn pass the relevant data (namely, is a particular photo a favorite or not?) into each PhotoListItem. PhotoList doesn’t actually need to know about the user’s favorites, so useEffect will enable us to skip PhotoList entirely. We simply import and call useContext at the top of the PhotoListItem component, which gives us access to state (we de-structure favorites off of it) and dispatch. No Consumer necessary!

Now, we need to add a “heart” icon to each PhotoListItem; if that photo is included in the user’s favorites, the icon is red; otherwise, it’s grey. When the user clicks that icon, the dispatch function is called with the action type TOGGLE_FAVORITE and a payload of imageObj, which we create at the top of the component and which includes the image’s url and id. Our PhotoListItem should look like this:

The dispatch call, which occurs when the icon is clicked, sends the action to the reducer function inside of reducer.js. The logic in the reducer function is outside the scope of this post, but suffice it to say that it looks through the user’s favorites in global state, and if the particular photo in the payload is in that array, it removes it. If it’s not in the favorites array, it adds it. The function then returns a new value for state , which triggers a re-render.

Ta-da!

Conclusion

I’ve called this post “A Beginner’s Guide to React, Part 2” because it builds off the material in a post I wrote in 2018, before the introduction of Hooks. The truth, though, is that Hooks are not really within the realm of “beginner’s React”; they’re still very new, and they take some time to get used to — even for seasoned React developers. If you’re confused — especially by useContext and useReducer — don’t fret! Start by practicing with useState and useContext, and work your way up to more complex Hooks. I highly recommend Reed Barger’s Udemy course on hooks if you’re looking for more guidance.

If you’d like to spend more time poking around in the code for this project, I’ve added the repo to my GitHub. And if you’d like to see the original, hook-less version of the project, it’s still on GitHub, as well.

Now, go forth and hook!

--

--