Synchronized state
Tags: javascript, statemanagement, state
Published: 2020-07-19 09:44:47

---
title: Synchronized state
published: true
description: Declarative reactive state updates
tags: javascript, statemanagement, state
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/01ggkj2qtc993xd9hqzr.png
---

#### TL;DR

State that should be updated when other pieces of state change can be modeled using regular state + **state synchronizers** that run after each state change.

When using [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting), they prove to be easy to maintain and compose.

[`state-synchronizers`](https://github.com/Gelio/state-synchronizers) is a library that makes it easy to use the idea of state synchronization for various state management solutions.

{% github Gelio/state-synchronizers no-readme %}

For a more in-depth explanation of synchronized state, read on.

# Different types of state

Applications often use state to decide what is shown to the user and which actions are available. There can be different types of state.

## Regular state

**Regular** state is what I will refer to as the **raw** state that can be changed and observed directly.

Regular state is the most common type of state. It can be the value of some input field that the user can freely fill in or the current route.

Regular state does not depend on other pieces of state.

## Derived state

There are times when one piece of state depends purely on other pieces of state. This is what is known as **derived state**.

The example that [**nas5w**](https://dev.to/nas5w) presents in [his great article on derived state](https://dev.to/nas5w/calculating-derived-state-in-javascript-using-selectors-3i15) is calculating whether the user is allowed into a bar based on the user's age and whether the user is an employee. This property can be derived strictly from other pieces of state, and can be saved either in the state management solution (e.g. `redux`) or derived outside of it (e.g. using `reselect`).

## A third type of state?

What if you need regular state, that has to change according to some rules when other pieces of state change?

For example, what if in a `Table` component you want to have a separate `currentPage` value, but it has to be at most `maxPage`, which is another piece of state, that is derived based on `pageSize` and `data.length`? All of the above should be available to the `Table` component.

Let's analyze the type of those pieces of state:

1. `data.length` - regular state, depends only on the data
2. `pageSize` - regular state, depends only on the user's preference
3. `maxPage` - derived data, depends on `data.length` and `pageSize`
4. `currentPage` - regular state (as the user can change it), but it should be at most `maxPage`

![Dependencies between state](https://dev-to-uploads.s3.amazonaws.com/i/01ggkj2qtc993xd9hqzr.png)

While it is possible to model `maxPage` using just derived data (e.g. using `reselect`), this approach does not work for `currentPage`. It has to be stored independently, as it can be changed without changing any other pieces of state.

This type of state is what I call **synchronized state**.

# Synchronized state

**Synchronized state** is a type of regular state that can depend on other pieces of state.

In a sense, it can be thought of as a combination of regular and derived state.

How to _synchronize_ (update the regular state) based on other properties after a state change?

## Regular state + additional updates

One way to synchronize the state would be to add the logic that updates the synchronized property in every place that the parent property is updated.

For example, when updating the `pageSize`, one could update `maxPage` and `currentPage`:

```ts
const onPageSizeChange = (pageSize) => {
  const maxPage = calculateMaxPage(pageSize, state.data.length);
  const currentPage = calculateCurrentPage(state.currentPage, maxPage);

  updateState({
    ...state,
    pageSize,
    maxPage,
    currentPage,
  });
};
```

This approach has the following cons:

1. Verbose - each time a piece of state is updated, all state that depends on this property has to be updated too.
2. Error-prone - it is possible to forget about updating one piece of state.
3. Hard to maintain - when adding new pieces of state that depend on the existing state, multiple places have to be modified.
4. Inefficient - in the code above, `currentPage` is always computed regardless of whether `maxPage` changed (`maxPage !== state.maxPage`). This could lead to unnecessary operations.

Let's explore other options that solve the problems listed above.

## State synchronizer

Instead of updating each piece of state individually, let's have a single **state synchronizer** function that would:

* update the synchronized state
* only update the state for which at least 1 parent changed

Such a state synchronizer could look as follows:

```ts
let previousState = {};

const synchronizeState = (state) => {
  if (state.data.length !== previousState.data.length || state.pageSize !== previousState.pageSize) {
    state.maxPage = calculateMaxPage(state.pageSize, state.data.length);
  }

  if (state.maxPage !== previousState.maxPage) {
    state.currentPage = calculateCurrentPage(state.currentPage, maxPage);
  }

  previousState = state;

  return state;
}
```

Then, when a piece of state is updated, before the update is saved, it should be passed to `synchronizeState`:

```ts
const onPageSizeChange = (pageSize) => {
  updateState(synchronizeState({
    ...state,
    pageSize,
  }));
};
```

### Further decomposition

When looking at the `synchronizeState` function above, one can notice that the function can be composed out of 2 individual state synchronizers - one for `maxPage` and one for `currentPage`.

```ts
function synchronizeMaxPage(state, previousState) {
  if (
    state.data.length !== previousState.data.length ||
    state.pageSize !== previousState.pageSize
  ) {
    state.maxPage = calculateMaxPage(state.pageSize, state.data.length);
  }
}

function synchronizeCurrentPage(state, previousState) {
  if (state.maxPage !== previousState.maxPage) {
    state.currentPage = calculateCurrentPage(state.currentPage, state.maxPage);
  }
}
```

Given these structure, the main `synchronizeState` function could be written as:

```ts
let previousState = {};

const synchronizeState = (state) => {
  synchronizeMaxPage(state, previousState);
  synchronizeCurrentPage(state, previousState);

  previousState = state;

  return state;
}
```

This approach scales easily to many state synchronizers. They will update the state only when necessary. There is a single function that can be invoked to apply all state synchronizations, so most of the goals set for the solution are met.

The only problem that remains is...

### Order of state synchronizers

One can misplace the lines and run `synchronizeCurrentPage` before `synchronizeMaxPage`, causing a bug - `synchronizeCurrentPage` would be using the possibly desynchronized `maxPage` variable, causing errors:

```ts
const initialState: AppState = {
  data: [1, 2, 3, 4],
  maxPage: 2,
  pageSize: 2,
  currentPage: 1,
};


synchronizeState(initialState);
const finalState = synchronizeState({
  ...initialState,
  pageSize: 4,
  currentPage: 2,
});

console.log(finalState);
```

The log on the last line will be:

```ts
{
  currentPage: 2,
  data: [1, 2, 3, 4],
  maxPage: 1,
  pageSize: 4,
}
```

`currentPage` is 2 even though `maxPage` is 1. The `synchronizeCurrentPage` ran first and used the `maxPage` from the previous state, which was not synchronized yet.

As you can see, the order of state synchronizers matters. For a few variables that can be easy to comprehend, but still some burden to maintain.

Fortunately, this problem can be easily solved by using one of algorithms of computer science - the [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting).

#### State as a graph

Dependencies between the state of the application can be thought of as a **directed acyclic graph**.

*Directed* means that links in the graph are unidirectional (child state depends on parent state).

*Acyclic* means there are no cycles (loops) in the graph. A cycle in the dependency graph would mean that state A depends on state B, state B depends on state C, and state C depends on state A. This scenario does not make sense, as then updates would never stop.

An example dependency graph is presented below:

![Dependencies between state](https://dev-to-uploads.s3.amazonaws.com/i/01ggkj2qtc993xd9hqzr.png)

Topological sorting can determine the order in which state should be synchronized. First, run any synchronizers for state without parents (`data.length` and `pageSize`, in arbitrary order). Then, run synchronizers only for those pieces of state, for which parents have already been synchronized. This means first running the synchronizer for `maxPage`, as both its parents have been synchronized, and synchronizing `currentPage` as the last item.

This order matches our *correct* order in the hardcoded version of `synchronizeState`.

## `state-synchronizers`

`state-synchronizers` is a library that makes it easy to apply the idea of synchronizing the state in your application.

{% github Gelio/state-synchronizers no-readme %}

The library exposes tools for:

* easily creating state synchronizers from plain JS objects
* composing state synchronizers to run in a deterministic valid order
* applying the state synchronization pattern to existing functions (e.g. [redux](https://redux.js.org/)'s reducers)
* synchronizing any type of state, not only plain JS objects (e.g. synchronizing [Immutable](https://immutable-js.github.io/immutable-js/) data structures)

Take a look at [the repository's README](https://github.com/Gelio/state-synchronizers) for more information.

To check the usage, take a look at the CodeSandbox below. It synchronizes the state of pagination that was explored in this post.

{% codesandbox state-synchronizers-65t0e %}

# Summary

State that should be updated when other pieces of state change can be modeled using regular state + **state synchronizers** that run after each state change.

When using [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting), they prove to be easy to maintain and compose.

[`state-synchronizers`](https://github.com/Gelio/state-synchronizers) is a library that makes it easy to use the idea of state synchronization for various state management solutions.