Refactor a Class Component with React hooks to a Function

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet

We have a render prop based <Query /> class component that allows us to make a GraphQL request with a given query string and variables and uses a GitHub graphql client that is in React context to make the request. Let's refactor this to a function component that uses the hooks useReducer, useContext, and useEffect.

Instructor: [00:00] Let's start out by making a function called query, and we're going to take all of the same props that we took before. We'll say query, variables, children, and normalize. Awesome. Normalize has a default value, so we're just going to use the destructuring default value right there.

[00:18] We've taken care of our props. Let's go ahead and pull the prop types onto our query now as a static property on that function. Then we can get rid of the prop types and the default props. Next, let's take care of the context type here.

[00:33] We're going to pull in useContext from here, and we'll get our client from useContext, github.context. Perfect. We got rid of that. Now, we need to take care of the state. There are a couple ways we could go about doing this.

[00:49] One way we could do this is, each one of these gets its own useState hook call, but instead, I'm going to change this as minimally as possible. What I'm going to do is, actually, we're going to pull in use reducer.

[01:01] Then in here, I'm going to get my state. I'm going to call this dispatch actually setState. I'm going to make this setState function behave similar to how setState works in a class component. Here, if we look, we're calling setState with an object here and an object here.

[01:19] We're not using the function version of this setState API. Let's go ahead and implement a reducer that implements that same kind of API. We'll say useReducer, and here, we're going to get our state and the new state.

[01:34] Here, we'll just merge in the state with a new state. Then we'll specify our initial state right here. Let's just pull that in from there. We've taken care of our state. Now, when this query component is mounted, it's going to call this query function, which basically just does all of this stuff.

[01:51] Let's copy this into a useEffect hook. I'm going to pull in useEffect. Then we'll come down here, useEffect. In our callback here, we'll just paste all of that stuff. Anywhere we see this, we can get rid of that.

[02:06] Because we implemented our useReducer reducer to behave similarly to setState, we can actually just get rid of this off of setState. Here, we're using this save setState, which we'll deal with later. We'll just call setState right now.

[02:23] Then we've also got this context. We're not going to need to get it from this anymore. We have access to it directly in our function. I'll just get rid of that line, and now, this client is referencing useContext there.

[02:35] Cool, all right. Then instead of this.props, we're getting our props directly from the function signature. I'm just going to pull this.props all three of those places, and get rid of that there. Sweet. We're off to the races there.

[02:49] Now, the next thing that I need to do is, when our component updates, we want to make that query again. We're actually doing that. Every single time our component updates, this callback function's going to be called.

[03:01] We actually only want to call this callback function when our query and our variables have changed. We're doing that in our old class component here with componentDidUpdate. We verify that the query and variables have changed.

[03:14] If either one of those have, then we'll re-run the query. We want to simulate that same behavior with our useEffect hook. As a second argument to here, I'm going to pass in my query and variables. Perfect.

[03:26] We've taken care of componentDidMount, with the exception of this._isMounted. We'll deal with that later, and our component did update. Perfect. Then we got our component will unmount. We'll take care of that later.

[03:39] Then we took care of the query as well, and our save setState, which we'll take care of later as well. We're just left with a render, and that's basically what we return from our function here, is just return. We're no longer using this props anymore.

[03:56] We just return children with the state. We're now getting that state directly from this user reducer. It's just a variable in the closure of our query component. We have our context coming from useContext. We're providing GitHub.context, which we're using the context type before.

[04:14] Then we're also getting our state from userReducer. We initialize that state, just as we were before. With our new state and our state, we merge those together, similar to how this.setState works in React.

[04:27] Then we have this useEffect, so that this callback will run when the component is mounted, and whenever the component is updated, in the case that query and variables has changed. We're going to rerun this update callback every time one of those changes.

[04:43] Then we return our children prop. We're going to invoke that to provide this nice renderProp API. One last thing to do here is we'll get rid of that class component. We'll get rid of this component here. We'll also get rid of low dash is equal.

[04:58] We'll save the file, and now, if I try to go to Kent C. Dodds, we're loading that data, and everything is working just as it was before. We can go to Matt Sabriski, and we load Matt's data. Our API for our query component hasn't changed one little bit.

[05:13] The implementation itself has changed, and been made quite simpler, by using React hooks like useContext, useReducer, and useEffect.

ed leach
ed leach
~ 3 years ago

That's a lot of dependencies. They must be used in the class from which this refactoring comes.

FYI - I had to delete the package-lock.json file before npm install would work. Then everything worked.

Thanks.

ed leach
ed leach
~ 3 years ago

What class is this project from? Thanks.

Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

I made this project for this course, but it's based on a workshop that my friend Matt Zabriskie and I gave several years ago.

Kent C. Dodds
Kent C. Doddsinstructor
~ 2 years ago

Every lesson in the course has a link to GitHub. The lessons are sequential and the code follows that sequence in branches on the GitHub repo. So if you look at the code in the previous lesson, that's the code before the next lesson.

Tony Catalfo
Tony Catalfo
~ 2 years ago

let squaresCopy=squares.map(xOrO) setSquares(squaresCopy) console.log(squares,squaresCopy)

Why is the array squares one move behind squaresCopy?

Tony Catalfo
Tony Catalfo
~ 2 years ago

const [squares, setSquares]= useState(Array(9).fill(null)) let squaresCopy=squares.map(xOrO) setSquares(squaresCopy) console.log(squares,squaresCopy)

Why is the array squares one move behind squaresCopy?

Akhlesh Tiwari
Akhlesh Tiwari
~ 2 years ago

Thanks for awesome course. If I already have stable react class components, should I move on hooks?

Kent C. Dodds
Kent C. Doddsinstructor
~ 2 years ago

I recommend having a real reason for refactoring existing and stable class components to hooks. I'd say you probably have better things to do with your time than refactor stable components for the fun of it.

If you need to make changes to a component though, then refactoring it to hooks first sounds great :)