Excalidraw state management
Tags: javascript, react, typescript
Published: 2020-03-20 13:18:12

---
title: Excalidraw state management
published: true
description: State management analysis of a popular React project
tags: javascript, react, typescript
---

[Excalidraw](https://excalidraw.com/) is a nice minimalistic drawing tool for block diagrams, sketches e.t.c.

![Excalidraw](https://dev-to-uploads.s3.amazonaws.com/i/kxksdrlvg8cldcls0nhz.jpg)

It was written by Christopher Chedeau, who works at Facebook. He worked on projects like [React Native](https://reactnative.dev/), [create-react-app](https://github.com/facebook/create-react-app), [Prettier](https://prettier.io/) and many others.

The project uses React and Typescript and is [open-source](https://github.com/excalidraw/excalidraw). I was interested what state management library top-notch frontend engineers use for their side projects nowdays.

Is it Redux? Redux Toolkit? MobX? Context API?

It turns out that no external state management library was used. Instead there is a custom mix of local component state and Redux.

I was interested in how this system works and I wrote a minimal example to reproduce Excalidraw's state management. There are three main blocks:

- **Actions**. They are like Redux reducers: recieve state and an optional payload and produce a new state with changes.
```ts
export const increment = register({
  name: 'increment',
  perform: state => ({
    ...state,
    counter: state.counter + 1
  })
});

export const decrement = register({
  name: 'decrement',
  perform: state => ({
    ...state,
    counter: state.counter - 1
  })
});
```


- **Action Manager**. This guy is responsible for registering and performing actions.
```ts
export class ActionManager {

  actions: {[keyProp: string]: Action};
  updater: UpdaterFn;
  getState: GetStateFn;

  constructor(updater: UpdaterFn, getState: GetStateFn) {
    this.updater = updater;
    this.actions = {};
    this.getState = getState;
  }

  registerAction = (action: Action) => {
    this.actions[action.name] = action;
  };

  registerAll = (actions: Action[]) => {
    actions.forEach(action => this.registerAction(action));
  };

  renderAction = (name: string, payload?: any) => {
    const action = this.actions[name];
    if (!action) {
      console.log(`No action with name ${name}`);
      return;
    }
    const newState = action.perform(this.getState(), payload);
    this.updater(newState);
  }
}

```


- **State**. The application state lives in the root `App` component and gets updated from `ActionManager`.
```ts
const initialState: AppState = {
  counter: 1,
  todos: []
};

class App extends React.Component<any, AppState> {

  actionManager: ActionManager;

  constructor(props: any) {
    super(props);

    this.state = initialState;
    this.actionManager = new ActionManager(this.stateUpdater, this.getState);
    this.actionManager.registerAll(actions);
  }

  getState = () => this.state;

  stateUpdater = (newState: AppState) => {
    this.setState({...newState});
  };

  render() {
    return (
      <div>
        <Counter actionManager={this.actionManager} appState={this.state} />
        <hr />
        <Todo actionManager={this.actionManager} appState={this.state} />
      </div>
    )
  }
}

```

When application starts the app state is created and a new instance of `ActionManager` is instantiated. Both `state` and `actionManager` are provided as props to every react component down the tree. When a component wants to make a change, it calls `actionManager.renderAction('someAction')`.

This is an interesting approach to state management which I did not met before. It has minimum boilerplate compared to the classic Redux.
There is props drilling with `state` and `actionsManager`, but it's not that bad. 
The business logic is nicely grouped in `actions` folder and can be easily accessed from any component from the tree.

Here's the [codesandbox demo](https://codesandbox.io/s/excalidraw-state-management-pu14y) if you're interested.