Application State Management
Tags: typescript, tutorial, javascript, beginners
Published: 2021-03-11 21:17:20

Welcome, everyone! In today's post, I would like to talk about the application state management. We will discuss what the state is and build a bare-bone state management solution with Typescript.

### What is the state and why we need it?
Application state is a piece of information held together and can be accessed from different parts of your application. Data stored in the state is a snapshot of your program's dynamic properties in a given moment.
Why do we need it?
- State helps to keep pieces of application in sync
- Centralization makes an application more maintainable and the code more readable

### Simple state
In an extremely simplified version, a state is just a JavaScript object. The state has some properties that different consumers can access. In the example below, our state keeps track of the count. `stateConsumerA` mutates the state by incrementing the count, while `stateConsumerB` logs state to the console.

```tsx
interface State {
  count: number;
}

const state: State = {
  count: 0
};

function stateConsumerA() {
  state.count++;
}

function stateConsumerB() {
  console.log(state);
}

stateConsumerA();
stateConsumerB(); // log: {count: 1}
```

What can we do better? One of the important requirements for the state is immutability. Immutability helps to prevent some undesired side-effects, which the mutation can cause. Moreover, immutability allows comparing different state snapshots to decide if an expensive operation should be performed. 

### Immutable state
Imagine your application being a public library and your state being a sacred book. As a library, you are willing to share this book's content, but you don't want it to be damaged. Therefore when someone requests that book, you send this person a copy.
Immutability in JavaScript is also achieved by creating a copy.

Consider an example below. We use an [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) to encapsulate the application state in the closure and expose methods to read and update the state.

```tsx
interface State {
  count: number;
}

interface StateStore {
  getState(): State;
  increment(): void;
}

const stateStore: StateStore = (function(): StateStore {
  const _state: State = {
    count: 0
  };

  return {
    getState: () => ({ ..._state }),
    increment: () => {
      _state.count++;
    }
  };
})();

function stateConsumerA() {
  stateStore.increment(); // original state count is incremented by one
  stateStore.getState().count = 100; // original state count is not mutated
}

function stateConsumerB() {
  console.log(stateStore.getState());
}

stateConsumerA();
stateConsumerB(); // log: {count: 1}
```
You might notice that instead of returning actual state value, we create its shallow copy. Therefore, when `stateConsumerA` attempts to mutate the state object, it does not affect the output from the `stateConsumerB`.

One could alternatively implement it using ES6 classes, which will be our preferred approach for the rest of that post.

```tsx
class Store {
  private state: State = {
    count: 0
  };

  public getState(): State {
    return { ...this.state };
  }

  public increment() {
    this.state.count++;
  }
}

const stateStore = new Store();
```

### Subscribing to state updates
Now, as you got an idea of what the state actually is, you might be wondering:
"OK, now I can update the state. But how do I know when the state was updated?".
The last missing piece is of cause subscribing to state updates. This is probably one reason why someone would bother about state management - to keep the application in sync.

There are a lot of brilliant state management solutions out there. But most of them have something in common - they rely on the **[Observer Pattern](https://en.wikipedia.org/wiki/Observer_pattern)**.
The concept is simple but though powerful. **Subject** keeps track of the state and its updates. **Observers** (in our case, state consumers) are *attached* to the subject and notified whenever the state changes.

```tsx
type Observer = (state: State) => void;
```
An observer, in our case, is just a function that takes `State` as an input and performs some operations with this state.
Let's create an `Observer` that logs if `count` is odd or even:
```tsx
function observer(state: State) {
  const isEven = state.count % 2 === 0;
  console.log(`Number is ${isEven ? "even" : "odd"}`);
}
```
Now we need to rework our `Store` class.
```tsx
class Store {
  private state: State = {
    count: 0
  };

  private observers: Observer[] = [];

  public getState(): State {
    return { ...this.state };
  }

  public increment() {
    this.state.count++;
    this.notify(); // We need to notify observers whenever state changes
  }

  public subscribe(observer: Observer) {
    this.observers.push(observer);
  }

  private notify() {
    this.observers.forEach(observer => observer(this.state));
  }
}
```

Let's look at this example. `Store`, our **Subject**, contains information about the state and allows *subscribing* observers to the updates by adding them to the list and invoking with the latest state snapshot when it changes.
Here it is in action:
```tsx
const stateStore = new Store();

stateStore.subscribe(observer);
stateStore.increment();
stateStore.increment();
stateStore.increment();
```

Our code will produce the following output:
```txt
Number is odd
Number is even
Number is odd
```
Although we haven't called our observer function, **Subject** does its job by *notifying* observers and calling them with the latest state snapshot.

### Last but not least
The example discussed in this post is not exhaustive. In the real-world scenario, you should also take performance into account and unsubscribe, or *detach* observers, when necessary.

```tsx
class Store {
  ...
  public unsubscribe(observer: Observer) {
    this.observers = this.observers.filter(item => item !== observer);
  }
  ...
}
```

### Conclusion

State management is an important topic. We deal with it regardless of the technology, and therefore I think it's important to know how it works under the hood.
Let me know if you find this topic interesting, and I will be happy to elaborate on this in the future.
If you liked my posts, please spread the word and [follow me on Twitter](https://twitter.com/gleb_irovich) 🚀  and **DEV.to** for more exciting content about web development.