Importance of state in JS apps

A necessary part of any serious application is being able to wrap your head around a basic idea that confuses many JS developers, who are often trying to learn a library or framework such as React or Angular and that is state.

State is not specific to frameworks; they are just forced on you

If you've already worked with these libraries before, know that state is not just a concept when working with JavaScript frameworks, it's necessary to understand for any app that you intend to make. What is different about tools like React or Angular or Vue is that they essentially force you to understand and use this concept of state within your applications for your own benefit. That's why many developers think that they have to jump right into learning a framework to build anything significant. But that's not the case. Once you understand state on your own as well as some robust patterns to manage it, you can use state to build powerful, reliable applications with just plain JavaScript, as we'll soon see in the next project.

State definition

What is state?

State is simply the data that has to be managed in our application, which comes from our users. The state of our app is all the data that we have to keep track of for it to work.

Identifying state

Take our first application. What was its state? Well, what data did we have to keep track of? For example, when a user typed into our form inputs. What did we do with that value? We stored it in variables and used it as we needed. So that form data was state. We had to keep track of note data from previous form submissions, the title and text of each note as well as its id, which we stored in an array. That's another piece of state.

App State Definition

All of these little pieces of data that we manage in our app as our user interacts with it form our app state.

Of course that's not all of the data in our app. We could come up with a number more examples, but that gives you an idea.

This is the thing that for developers new to the idea of state that is hard to grasp. From the simplest todo app to Facebook, when a user interacts with an app, the data from those interactions form our state. As a result, with our app or any app, there is always state.

Imprecision of state

State is kind of like the term API, because it is in so many places that is hard to give it a precise definition. But once you see examples of it and know what it is, you realize it is everywhere.

State is data over time

You might be asking why we need this word state. If state is just data in our application, why don't developers just refer to it as app data?

The reason we talk about it as state is because we need to have a way of talking about how things in our application change as our user interacts with it and what we need to keep track of. If it’s a value that we need to hold onto, we need to keep it in state. A good example of this for both applications is holding onto the values that users type into form inputs. To hold onto these values after they were created, we put them in variables to use later, such as when the user submitted the form.

Take for example a user logging into our application. Before the user does so, we’re going to have a noticeably different state than after they have logged in. After logging, we would expect that our state is going to include a number of new values, depending on what our application needs to have access to. For example, we would likely want to have their username, email and maybe the user’s avatar as well.

State -> status (of our app)

If the word state trips you up, I like to compare it to a similar word in appearance—status. State is important because it tells us the status of our application.

Benefit of state management

So at this point, we know that state is a part of any application, but now the question is--why would we want to 'manage' it? And how would we go about doing that?

The benefit of state management is that makes the state of your app, this invisible collection of data that we've been talking about visible. And we do that by making it a data structure where we can either get those values, read from state, or update those values, set state, at any time.

App that doesn't have a state management strategy vs one that does

To see how state can be better managed, let's take a look at the difference between letting state just exist, so to speak, as compared to how we can manage it:

Right now we have a simple example with a class. This app has a simple purpose, to greet our user if there is one, or if there isn't to show an error with a red color. So we are showing one or another message based on a given state.

Our app is being run entirely by JavaScript. We have this render method which is setting the HTML of our app. Then we are reaching into the DOM to find the element with the id of user-message. Then we are using this checkAuth function to dynamically change the text content of the userMessage element according to whether we have a user message or not.

To test this out, our app greets our user if we have one (if user is true), and if user is false, we show our error message telling them to sign in.

So how are we managing state? At this point, we're really not, and this is how most JavaScript apps look, maybe even ours at some times. Do we know what our state is? Not easily, but if we look into the checkAuth method, we see that there's some user state that's coming from somewhere. If we look closely at our program, namely at the conditional in checkAuth, we guess that there is an error state as well, but we're just inferring that because the text is being set to red, it's not been written anywhere. We can't expect that anyone looking at our code will be able to easily tell what state values this class manages.

This is an important part of managing state, not just doing something with the values we're holding onto, but communicating to other developers what stuff we care about. So we need to present those values in a more readable place.

The second question as to whether we're managing state well is where our state lives. It doesn't live in an obvious place. Other developers likely have to look at the content of userMessage element to figure out the state. So part of is in a certain method and the rest is in the DOM. In other words it is spread out.

So we can see that it would be an improvement to rewrite this code so that the state, or the values that we are keeping track of are in an obvious place for any developer to find (without having to read through all of our code) as well as having state live in a more centralized place, rather than in random places around our class.

State as a single source of truth

Let's introduce an idea to help us out. When we write our classes or any other data structure we use to organize our app, we want manage our state to operate as our single source of truth. Meaning if we want to figure out what the status of our app is at any moment, we look to where we store our state.

And the way that state is most consistenly managed across the most popular JavaScript libraries is using objects, we've already seen its power as an organize data structure. So our state in this class will live in a dedicated state object, which we'll create at the top of our constructor:

constructor() {
    this.state = {};
  }

If any developer wants to know what state values we're keeping track of and matter for this class, they look here. So what are they again? What are the pieces of data that we care about? We care about whether we have a user, because that determines what message we show, and we also care about whether there is an error. So both isAuth (meaning isAuthenticated, if we have a user), and error will be properties of the state object. isAuth will be one or two states, true or false. The user is either authenticated or they're not and error will store the error message, as a string.

Now, revisiting this idea of having our state object be our single source of truth, we want to rewrite the functionality of our code to rely on the values that we have in state at any given point in time. How do we do that?

First of all, we want to set state or update state accordingly. That's really what the use of our checkAuth function was for. So here, instead of immediately putting our app state in the DOM, we update state. If user is true, then isAuth should be true in state. We don't want to mutate state directly for reasons we've already spoken about, so instead of writing this:

if (user) {
  this.state.isAuth = true;
}

We'll do a shallow clone of the state object with the spread operator and just update the value we want to change:

this.state = { ...this.state, isAuth: true };

The same for the error text:

this.state = { ...this.state, error: "You must sign in!" };

Note that we can now have a more logical order in our constructor, create state, check auth, and then render the HTML, after we have our data, because what we display in page is dependent on our state:

constructor() {
    this.state = {
      isAuth: false,
      error: ""
    };
    this.checkAuth();
    this.render();
  }

We can get rid of the userMessage reference entirely. We don't need to dive into the DOM to change our text. Now with our state, we can determine how to structure our rendered content: If we isAuth is true, if we have a user, we show our success message as before, but if we don't, we show our error message contain in error state. And we can write all of this using an interpolated ternary:

render() {
    document.getElementById("root").innerHTML = `
      <div>
        ${this.state.isAuth ? "Welcome back!" : this.state.error}
      </div>
    `;
  }

And using the power of destructuring, we can make this even more legible by getting the properties we need from this.state:

render() {
    const { isAuth, error } = this.state;

    document.getElementById("root").innerHTML = `
      <div>
        ${isAuth ? "Welcome back!" : error}
      </div>
    `;
  }

If other developers, don't understand what is going on based off of the state object, they can see it represented in here in the HTML, too. The state in HTML reflects the state stored in the state object. So this respects the single source of truth principle.

And finally, to take care of the red text for our error message, we can use shortcircuiting by using inline styles on the enclosing div. For the color property, if we have an error message, if it's truthy, then return the value 'red':

render() {
    const { isAuth, error } = this.state;

    document.getElementById("root").innerHTML = `
      <div style="color: ${error && "red"}">
        ${isAuth ? "Welcome back!" : error}
      </div>
    `;
  }

And to test this out, if user is true, we greet our user, otherwise we display our error message.

Review

My challenge to you is to look at the starting version of our code with all of it's DOM manipulation and compare it with our state-driven second version. What makes more sense to you? Which is easier to read? Why?

Go through this lesson again if you need to digest the approach that we've taken here and the benefits of it. But they should be pretty clear if you understand all the ideas represented--using state objects to declare and manage important data, updating state immutably, and state as a single source of truth to determine the renderered content of our app.

Using reducers for state management

Now that we have a solid understanding of what state is, how to identify it in our apps and the benefit of properly managing it, we'll need some new patterns with JavaScript to better manage and work with pieces of state in our apps, especially considering that our next app with have more features and therefore more state to manage than either of our previous apps, so with that let's look at a powerful way of managing state with a new pattern called a reducer.