React State Management: Class vs Hooks Components

Victoria Rodriguez
Webtips
Published in
5 min readJul 23, 2020
Photo by Dean Pugh on Unsplash

What is State?

In React, state is the relationship between data, rules you’ve given to the app, and what shows on the page. It can be broken down into five categories: model state, view/UI state, session, communication and location state. For example, if you are creating an e-commerce web application, here’s how to interpret each kind of state under this context:

  • Model State: The nouns (things) in your app, the list of your apparel (products), each individual product, the price/ description of those products that’s likely stored on the server. Model state is the thing that’s persisting in your application.
  • View/UI State: How is the list of products sorted (ascending/descending)? Is the list currently being filtered? We have the info of the product from model state, but how do we view it? Are we viewing the products by how popular they are? Or are we viewing them by their price from high-low? Unlike model state, information about UI state is not usually stored in the database but on the client-side of the app.
  • Session State: Is the user logged in? What kind of user are they? Are they a consumer user or an admin user? This determines what information they can see while using the app.
  • Communication State: Are we loading the app? Has it already loaded? Are we updating it? Was there an error? Are we in the process of fetching model state from the server? Communication state is the process of communicating with other kinds of state mentioned.
  • Location State: Where are we in the application (think URL)? Are we browsing products? Are we in the shopping cart? Are we on the checkout page?

For the rest of this article, I’ll be referring to model state when we take a closer look at React’s class-based and hooks components in a counter exercise.

Class-Based State

Let’s start with a simple counter that increments and decrements by one with a Class component. Here’s what the starter code looks like:

Notice the render function return has a hardcoded value of 0. To allow for incrementing and decrementing, we can change that by incorporating state in a constructor function. In the constructor function, pass in props and pass props to the super method(). Props represent properties, which in React is used for passing data from a parent component to children components. We assign local state by putting this.state in the constructor function.

Now let’s think about what the state of this counter app is. What is the thing you see that will change when increment/decrement is clicked? It’s the number, the count, the 0 that we hardcoded earlier. In this.state, let’s assign the 0 to a key called count. Then replace the hardcoded 0 in render() with {count}, and we can now dynamically render our count. You can assign count any number that you want this counter to begin from. Your code should look like this:

Let’s write some methods to change our count — the model state — since it is currently stuck as 0.

Under the constructor function, let’s create an increment method, this method will call setState on the class and add 1 to count using this.state.count + 1.

Use the same logic to create a decrement method. This time subtracting one.

Lastly, we need a reset function to set the count back to 0.

Next, we bind these methods to the corresponding increment, decrement, and reset buttons. When the onClick event is fired, we need to call the method that does what we want. For the increment button, onClick will be bound to {this.increment}, and the same logic applies to decrement and reset.

The last thing to do before your counter works properly, we have to bind the three methods to this inside the constructor for our current Class so the app doesn’t lose track of what “this” is in the event queue. You code should look like this:

Hooks State: useState

Hooks are a new pattern introduced by React in 2018 that let us write much less code, but still use state and side effects in functional components. Let’s see how state is managed with hooks by refactoring the same counter example. Go ahead and delete everything from the start to render. We will start writing hooks from here:

Notice we have gotten rid of A LOT of code. Where does state go if we don’t have a constructor function? This is where useState() comes in, we assign our initial state in the useState function. Go ahead and pass 0 in the parens since the counter begins from 0. What about the array of arguments on line 4? Inside the array, the first element -count- is the current value on the state and setCount is a function that lets you set the value.

Let’s use setCount to make the counter work! Write a function called increment and return setCount invoked with count value + 1. Create two other functions with setCount that decrements and resets the counter. Lastly, get rid of “this” inside the buttons. Your counter works the same as it did when it was a Class component. Except it’s much cleaner without the event binding with this. The code looks like this:

Hooks State: useReducer

So far state management seems pretty straightforward in our counter app with useState(). Before hooks, the way to manage a complex state is by using Redux. If you already know Redux, the useReducer hook is like a simplified version. Let’s explore how state is managed with the useReducer hook.

Let’s start by writing a function called reducer. Reducer will take two arguments: state and action. It returns a new state object depending on the type of the action we pass in. In the counter example, we have three types of actions: increment decrement and reset. Whenever you create a reducer function, think about the type of actions you can perform on your state. We could write if-statements to handle different action types, but a much clearer way is to use switch. It’s a good idea to have a default state (the current state) in the switch state, if we are not performing any actions, simply return the state we already have. Here’s what the reducer functions look like:

Notice the action types are all strings, this will make debugging harder and more prone to errors when we misspell the strings! Let’s change that by declaring a constant called ACTIONS in the global scope. In your reducer function, instead of using strings, you can now use something like ACTIONS.INCREMENT. See the example below:

Now let’s implement the reducer function as an argument for useReducer. In the Counter component you call useReducer with two arguments: the reducer function and state. It returns an array of 2 elements, the first element is the current state, and the second element is a dispatch function. The dispatch function is called in order to update state. We pass an action type in the dispatch function, and the reducer returns the new updated state according to what type of action is passed in. Let ‘s check out the code:

Conclusion

We’ve now seen three different ways to manage state in react applications: 1) setState in Class-based components 2) useState in Hooks 3) useReducer in Hooks. How you choose to manage the model state depends on how you want the other states to exist. If you are interested in talking more about React state management, connect with me on Linkedin or leave a comment on the GitHub gists in this article!

--

--