To build any serious JavaScript application you must understand an idea that trips up many developers—state.

What is state?

If you've attempted to learn React or Vue, you know state is an essential concept for these libraries. Many JavaScript developers attempt to learn a library and then figure out what state is and what to do with it.

This is the backwards approach.

State exists in every application, even those made with plain JavaScript. It's not a concept specific to JS libraries; it's necessary to understand for any app you intend to make. Libraries use this concept of state within your applications for your own benefit.

The state of our app is all the data in our application that we have to keep track of for it to work.

To better understand what that means, let's take a simple application and see how a user interacts with it:

state management gif

What is this application's state? Let me ask you: what data does the app itself have to keep track of?

Whether a user is signed in or a guest. And based off of that data, the user message shows the authenticated message "Welcome back!" or the unauthenticated message "You must sign in!". So the authentication status is one piece of state.

Anything else? Notice that the color of the message changes to red from black when the user is a guest (unauthenticated). That might represent an error state. That's another piece of state.

The challenge of understanding state

All 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 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.

With our app or any app, there is always state.

This is likely the reason why beginners don't grasp state. State 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 nothing more than data in our application, why don't developers refer to it as app data?

One reason we have a specific term 'state' is to describe how our application change with the data over time.

Take for example a user logging into our application with their name and email. Before the user does so, we’re going to have a different state than after they have logged in (no user data then). After logging in, our state will have some new values, depending on what our application needs. For example, our app might need to store a username, name and email for the login.

State as app status

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 at any moment. Think of it like an individual frame of a movie.

state as status diagram

The value of state management

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

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.

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. Let's take a look at the code for the app we saw in action:

Even if you don't understand all the code, know that our app has a simple purpose.

We greet the user if they are signed in, or if they are a gust, 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 controlled by JavaScript, resembling the structure of a single page app (i.e. React). In our code, we have a 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. We're also listening for the option value to change by listening to the change event. Then we're passing that selected value to check auth to determine what text to display.

So how are we managing state? At this point, we're not. And the way that we know this is by asking the question: would another developer looking at our code be able to identify our state by looking at our code?

If we look into the checkAuth method, we see that there's some user state, but that's not obvious. If we look closely at the conditional in checkAuth, we could guess there is an error state as well. But's that not explicit since the word 'error' doesn't appear anywhere. We can't expect that anyone looking at our code will be able to easily tell what state values this class manages.

Managing state is to a great extent having clear code that declares what it does. We want to communicate to other developers what stuff we care about. So we need to present those values in a more readable place.

Aside from the readability of our code, we can say whether we are managing state based on where that states lives. Right now it doesn't live in an obvious place. Other developers likely have to look at all parts of our app to figure out the state. So part of our state is in our functions and the rest is in the DOM. It's spread out.

Let's rewrite this code so that the state, the values that we are keeping track of, are in an obvious place for any developer to find. It's better to have it live in a more centralized place, rather than in random places around our class.

State as a single source of truth

Before we rewrite anything, 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.

One popular way to manage state across many JavaScript libraries (such as React and Redux) is using objects. 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, for our user's status), 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.

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

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

First of all, we want to set state or update state. 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.

Best practice: update state immutably

When we update state, we want to do so immutably. That means we want to copy the previous state before making any updates to make sure the new state doesn't reference an old, incorrect value. State updates should always be immutable because often the new state depends on the previous state. So instead of writing this:

if (status === "auth") {
  this.state.isAuth = true;
}

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

if (status === "auth") {
  this.state = { ...this.state, error: "", isAuth: true };
}

The same for the error text:

else if (status === 'unauth') {
  this.state = { ...this.state, isAuth: false error: "You must sign in!" };
}

Spreading in the remaining state may not be as necessary here, but it is when there are many properties on a state object that need to be preserved between state updates.

And now we can get rid of the userMessage reference altogether, both in checkAuth and the constructor. We don't need to dive into the DOM to change our text.

What's changed about how the app is rendered? The state will determine the UI, which means that our app must be rendered in response to a state update, so we must call this.render() after updating state:

constructor() {
  this.state = {
    isAuth: false,
    error: ''
  };
  this.$authStatus = document.getElementById('auth-status');
  this
    .$authStatus
    .addEventListener('change', event => {
       // update state with checkAuth...
      this.checkAuth(event.target.value);
      // ...then render app to display new state
      this.render();
    });
}

Now with our new state, we can determine how to structure our rendered content. If this.state.isAuth is true (if we have a user) we show our success message as before, but if it isn't, we show our error message contained in error state. And we can write all 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 the and operator (&&) with 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>
    `;
  }

Here's the final version of our code with our managed state:

Summary

My challenge to you is to look at the starting version of our code with all 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 see the value of the following ideas:

  1. Use state variables / objects to declare and manage important data

  2. For predictable outcomes, update state immutably, and—

  3. State should serve as single source of truth

What's Next

In this lesson, I wanted to get across the fact that state is essential for all JavaScript developers to understand. You don't need to wait until you start learning React.

However since React was created with the concept of state in mind, we can now move on to learning about how React enables us to leverage our understanding of state to build apps more easily than with plain JavaScript alone.