The 5 Types Of React Application State

I don’t know about you, but when I started writing React apps, I struggled to decide where my state should go. No matter how I re-arranged setState calls, things never felt quite right.

"Messy state"

And maybe that is why I got so excited when I found Redux. Redux gave me a single place to put all my state. Which sounded great in theory.

"What if"

But then I realised that having one spot to put things doesn’t necessarily make them easy to get to.

"Still messy"

It turns out that I needed more than just a place to put things. I also need a system for putting them in the right place.

"Banzai!"

For me, that system came from splitting my state into five categories. It turned the problem of deciding “how does this piece of state relate to all the other state”, into the problem of deciding “what type of state is this”. And as it turns out, this is a whole lot easier.

So here’s the deal. React apps have five types of state. Each type of state has a number of rules which it follows. It interacts with the other types of state in well defined ways — as long as it follows the rules. And based on these rules, you’ll find that there are ways of storing the state which just make sense.

So without further ado, your five types of state are:

  • Data
  • Communication
  • Control
  • Session
  • Location

And of these five, “Data” is the one which really needs a better name. It also happens to be the easiest one to explain. So let’s dig in.

Data

Data state covers information which your application temporarily stores about the big wide world. That is, it covers your business data.

Now let’s say you’re building something hip and cool like an invoicing application. Examples of the Data state for you application will include things received from the server — invoices, contacts, receipts, etc. And, as all of your data comes from the outside world, it is probably going to have some sort of identifier which you use to request it:

GET the /invoice with ?id=happiness, or something

Actually, I’m going to enshrine this as The Rule of Data state:

Every piece of received Data has a type, and a selector which exactly specifies which data was received.

This fact makes it easy to design a Redux store for our data; at minimum, it is going to include a way to map a type and id to a received object. It will also include actions to update the store with received objects.

"Data goes on shelves"

And this is awesome, because now we have a data store which can be accessed from anywhere within the application using connect. Also, as long as your data follows the rule of data state, you can spice things up with indexes and custom higher order components too.

Of course, Data doesn’t just magically appear in an application. We need to request it, and then wait until the request succeeds or fails. And that is where Communication state comes in.

Communication state

This type of state covers the seemingly simple yet somewhat thorny information which represents things like loading spinners and error messages. It also covers the state which you may not have realised you introduced into your application when you passed a callback to a HTTP request.

But the above explanation is pretty complex. To simplify things, lets forget about what Communication state does, and instead think about what it is. In fact, The Rule of Communication state covers this:

Communication state is the status of any not-yet-complete requests to other services.

This means that all of the following are communication state:

  • The type/selector for any Data you expect to receive
  • The type, selector and expected change of any operations you have requested on Data
  • The error messages for anything which didn’t go quite as planned

"Communications goes in queues"

Defining Communication state this way has two major benefits:

Speaking of Communication benefits...

  1. You can store it with a simple Redux reducer which manages an array of Request objects
  2. You can now compute the status (retrieving, updating, fuckwhathappened, etc.) of any Data in your store with a pure function of your Data and Communications state.
  3. No srsly. That means that now you never have to write a setState({ saving: trueorfalse }) ever again.

So both Communications state and Data state can be implemented as independent stores, managed by Redux. They’re both accessible to your entire application using connect. I’m starting to see a pattern here — maybe all state fits well into a certain type of Reducer which the entire application can make use of it? If only it were that simple.

Control state

In contrast to the above two types of state, Control state does not represent the application’s environment. Instead, it refers to the state which the user has input into the app. Form inputs, selected items, things like that.

Things like wha? Yeah, so you may have noticed that forms and selected items are completely different types of information. Selected items probably take the form of a single string representing an id, while forms can be huge nested objects. And that is the thing about control state — its shape doesn’t really follow any pattern.

Luckily though, it does follow another type of pattern. You see, the thing about control state is that it is generally specific to a single view. Or screen. Or container component. Which brings us to The Rule of Control state:

Control State is state which is specific to a given container component, and which is not stored in the screen’s URL or in the HTML5 History API.

"Each view's control state gets its own box"

Great, so we have a pattern to work with. Now how do we use that pattern to write Redux reducers for our views? Have a little think about it, and once you’ve got an answer, check it by hovering your mouse over or touching the box below.

You don’t.

Did you think about that for a while? Sorry. But I think this is an important point to make. And Dan Abramov, author of Redux, seems to think so too:

Don’t use Redux until you have problems with vanilla React

Remember how Data and Communications state needs to be available to the entire application? Control state only needs to be available to a specific view. And that means that setState — i.e. vanilla React — is perfect.

Actually, I just glossed over an important point, so let me spell it out a little better:

You don’t have to use the same method for storing every type of state. Seriously.

Do you have state which has a predictable shape and needs to be available everywhere within your application? Use Redux. What about state with an unpredictable shape which is limited to one view? Use setState. Maybe you have state which needs to be available everywhere but has an unpredictable shape? Uhhh…

Session state

When you have information which needs to be available application-wide, but its shape is less well defined than your project’s schedule, it is probably Session state.

Or put it another, and we get The First Rule of Session state:

Session state contains information about the human being which is currently using your application.

This includes the obvious stuff – their user id, permissions, etc. But it also may include a user’s preferences for how the application should work.

Now one thing about session state is that parts of it can be pretty similar to Control state. For example, you may have a piece of Control state which represents which parts of a tree view are collapsed or expanded. You may also have a piece of Session state which represents … exactly the same thing. But I promise you there is a difference. And you can probably figure it out yourself with The Second Rule of Session State:

Session state is only ever read when a component is mounted.

Of course, this means that the Session version of your tree view’s expansion is a copy of the Control state. Sure, your view can write to the session whenever it wants. But it only reads from it to initialise the view. Or to put it another way: Session state can be used to save preferences.

Ok, cool. So how does session state get stored? Like this:

"Session state comes from above"

Session state rains down from above. In the React world, that means it is probably just a plain old object which has found its way into your component’s context. You’ll probably want some way to make sure it doesn’t disappear between page loads, but I’m sure you can figure that out. But before you do, let’s talk about the baddest, meanest state of them all.

Location state

What counts as location? Intuitively, I’d say “anything which you can give someone concrete directions to”. But a wishy washy explanation like that isn’t going to get us very far. So let’s try something concrete:

Location state is that UTF-8 mess which appears in your URL bar.

This definition has a number of things going for it:

  • That UTF-8 mess definitely represents the state of your application
  • Sharing it between users is kind of like giving directions
  • The L in URL stands for Locatooor

But as great as this definition is, it isn’t really complete. For a start, it doesn’t take into account the fact that you can give people directions to parts of your application which don’t have a unique URL. Also, the HTML5 History API actually lets you store state separately from the URL. Aptly, you do this by calling a method named pushState.

To be honest, I haven’t really succeeded in pinning down an accurate definition of Location state. So I’ve gone with a pragmatic one instead. And it also happens to be The Rule of Location state:

Location state is the information stored in the URL and the HTML5 History state object

Handily, this definition covers both the type of information which is stored, as well as the method for storing it. Unfortunately, it doesn’t help us with the structure. Instead, it forces us into storing one of the most important pieces of state in our entire application as a bloody string.

And it goes without saying that no competent webmaster is going to build a large scale web application with routing based on window.location.hash.indexOf. But one of the funny things about URLs is that while they’re stored as strings, they don’t represent strings. They actually represent a hierarchy. And that happens to overlap with your application’s component hierarchy.

"Location is a subset of a tree"

In fact, by combining the hierarchy stored in URLs with the ability to store extra location with pushState, you can build a location tree which maps to a (hopefully proper) subset of your application’s component tree. Or to put it another way, your location state ends up being a set of flags which enable and disable branches of your application. Simple, right?

It would be, if it wasn’t for side effects.

Side effects

Of our five types of state, four of them mostly mind their own business. Changes to Data, Communication, Control and Session state will generally not cause other types of state to change as well.

Location state is a whole different ball game. Every time it changes, you’ll find other state changing too:

  • Changing Location mounts container components, causing Control state to change
  • Changing Location makes HTTP requests which cause Communication state to change, before also causing Data state to change
  • Changing Location can even cause Location to change! That’s what redirects do.

So here’s the thing about having your state structured well: you suddenly realise how much of a pain in the arse uncontrolled side effects are.

Luckily, they don’t have to stay uncontrolled. That’s what controllers are for. Want to find out how to use them? Then subscribe to make sure you don’t miss out on Episode 4 of State Of React, where I discuss the C in MVC! I’ll even throw in 5 free ReactJS and JavaScript cheatsheets:

I will send you useful articles, cheatsheets and code.

I won't send you useless inbox filler. No spam, ever.
Unsubscribe at any time.

One more thing – I love hearing your questions, offers and opinions. If you have something to say, leave a comment or send me an e-mail at james@jamesknelson.com. I’m looking forward to hearing from you!

More Reading

8 Comments The 5 Types Of React Application State

  1. Craig Smitham

    Good write up. Curious how you structure your communication state/request reducer. Here’s the pattern I follow:

    1) For ‘data’, I usually refer to this state as ‘entities’ which is usually a map ids/objects
    2) For ‘communication’, I’ll usually define an operation context, which is usually just some booleans as part of my state (creating/updating/deleting etc)

    An interesting scenario is paging. I’ll usually have a list of IDs along with a boolean or two to indicate ‘communication’ state.

    So, my state might be structured like this:

    {
    users: {
    entities: {
    “1” : { … }
    },
    friends: {
    ids: [“1”, …],
    fetching: false
    }
    creating: false,
    fetching: false,
    editing: false
    }
    }

    You can see how I’m commingling my communication state with my other state information. Curious to see what your take is on this. This works for me, but sometimes it feels out of place. Maybe this communication state could live in controls, maybe it needs its own reducer?

    Reply
  2. David Tuite

    What does your communication state look like exactly? You mentioned it’s in a separate reducer and you never have to call setState({ saving: false }). If that’s the case, how do you indicate that a request has completed?

    Lets say I have some payments in my application which I request from the server:

    {
    payments: [{
    id: 1,
    amount: 12,
    loadingState: ‘loaded’,
    }, {
    id: 2,
    loadingState: ‘loading’,
    }],
    }

    How would you represent that communication state in your application?

    Reply
  3. Mirko Stocker

    Nice article! Just one question, on the communication state, you recommend a “reducer which manages an array of Request objects”. Could you elaborate on what you store in this Request object?

    Reply

Leave a Reply

Your email address will not be published.