How to implement state management inside a simple JavaScript app
Tags: javascript, webdev, statemanagement, beginners
Published: 2023-01-31 05:22:01

## Motivation

Not every application needs React or Angular or another fancy, trendy framework.

What i see in every junior developer is that they are tackling every problem using frameworks even though there's no need to use them at some point.

So in this article, i will show you how to handle your application using Vite and JavaScript (my article, my rules 😋)

## The main structure

Okay, let's start from the beginning, the core of our app by running this simple command:

```bash
yarn create vite
```

Of course, you can use any package manager you wish to.

Give a project name when asked, in my case i choose "greatapp" (not an original app name but deal with it, ok? 😆), then choose **Vanilla** and **JavaScript** in the last question.

So, now you should have something like this:

![folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tws8cx8xp7oqieio7crf.png)

Let's run the app by typing `yarn dev`

Here is what we got:

![first run](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wksprwo95ta52hq8kcsh.png)

Go on and try to click on the button. It works! The amount is incrementing!

Hum... but what if i want to decrement it? 😅
Still following? Great! Let's jump into the next section

## Components

Apps are often divided into small pieces that we call _Components_. Separating our code into Components will help use to maintain and grow our app easily and even debug.

Let's look at the _counter.js_ file. If you are coming from a React background, you can see that it is similar to a component which have a state changing when an action is done.

Well, what i want to do is to isolate buttons into components and assign actions to them.

Let's create it. Start by creating a file named _Button.js_ (you can choose another name but it's more relevant to me to call it like that 😎)

Now let's add some code inside it.

```js
export const Button = (element, text) => {
  const button = document.createElement("button");
  button.innerHTML = text;
  document.querySelector(element).appendChild(button);
};
```

Ok, let's break it down for a minute. We are creating a component called _Button_ with _elements_ and _text_ as parameters where _element_ is the HTML element we will add our component to, and _text_ will be the text displayed on the button.

Let's use it inside our main function by changing it to make it use our newly created component:

```js
import "./style.css";
import javascriptLogo from "./public/javascript.svg";
import { setupCounter } from "./counter.js";
import { Button } from "./buttons";

document.querySelector("#app").innerHTML = `
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
      <img src="${javascriptLogo}" class="logo vanilla" alt="JavaScript logo" />
    </a>
    <h1>Hello Vite!</h1>
    <div class="card">
      <button id="counter" type="button"></button>
      <p id="counter"></p>
    </div>
    <div id="buttons"></div> 
  </div>
`;

setupCounter(document.querySelector("#counter"));

Button("#buttons", "-");
Button("#buttons", "+");
```

Oh my! What's going on here 😱! We've added our _Button component_ by importing it and we've add two instance of it at the bottom. As you can see, _Button components_ will be rendered inside a _div tag_ created above:
```js
<div id="buttons"></div>
```

Also, i've changed the element counter button into a simple paragraph element as we will change its state by using our _Button component_

With the style added by default by Vite, our buttons will look so fancy! 😍


![buttons added](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lw6zygnrrqx0q5fxh4g7.png)

## Actions

Okay, we've added our buttons but they are useless for now. Let's change that and add some events to our _Button component_:

```js
export const Buttons = (element, text, action) => {
  const button = document.createElement("button");
  button.innerHTML = text;
  button.addEventListener("click", action);
  document.querySelector(element).appendChild(button);
};
```

Now we've added an action which is a function executed when the user will click on our button. Let's try again to use it so we make sure it works by changing our _main.js_

```js
import "./style.css";
import javascriptLogo from "./javascript.svg";
import { setupCounter } from "./counter.js";
import { Buttons } from "./buttons";

document.querySelector("#app").innerHTML = `
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
      <img src="${javascriptLogo}" class="logo vanilla" alt="JavaScript logo" />
    </a>
    <h1>Hello Vite!</h1>
    <div class="card">
      <button id="counter" type="button"></button>
    </div>
    <div id="buttons"></div>
  </div>
`;

const action = () => {
  console.log("Hello from button");
};

setupCounter(document.querySelector("#counter"));
Buttons("#buttons", "-", action);
```

When we click on our buttons, we can see that our function is running correctly as it will display `Hello from button` inside the console.

Great! Let's try to add a state management to handle this variable.

## State management

### 1- What is a state management?

State management refers to the process of managing data or state in an application, particularly in single-page applications or in applications with complex user interfaces. 

The goal of state management is to ensure that the data displayed to the user and the user interactions with the application are consistent and predictable, even as the data changes over time.

I hope this is clear enough 😊

### 2- Attempt 1 - Global variables

Ok, first, we can think of a way to implement this by creating a file holding the global variables and call them inside other file. Seems great? Let's try!

Create a file at the root of our project, i will name it _state.js_ (again you can name it whatever you want) and let's put this code inside it:

```js
// state.js
export const $counter = 0; // "$" sign is just a way for me to differenciate global variables
```

Basically, we are just exporting a simple variable with an initial state of 0.
Now, let's use it inside our app:

```js
// counter.js
import { $counter } from "./state";

export function setupCounter(element) {
  const setCounter = () => {
    element.innerHTML = `count is ${$counter}`;
  };

  setCounter();
}
```

```js
// main.js

import "./style.css";
import javascriptLogo from "./javascript.svg";
import { setupCounter } from "./counter.js";
import { Buttons } from "./buttons";
import { $counter } from "./state";

document.querySelector("#app").innerHTML = `
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
      <img src="${javascriptLogo}" class="logo vanilla" alt="JavaScript logo" />
    </a>
    <h1>Hello Vite!</h1>
    <div class="card">
      <p id="counter"></p>
    </div>
    <div id="buttons"></div> 
  </div>
`;

const increment = () => {
  $counter = $counter + 1;
};

const decrement = () => {
  $counter = $counter - 1;
};

setupCounter(document.querySelector("#counter"));
Button("#buttons", "-", decrement);
Button("#buttons", "+", increment);
```

🥵 ouf! Now, let's do some test!
❌ Uh-oh 😱! What's this? I'm getting an explicit error saying `Assignment to constant variable`.

Hum...🤔 Oh silly me! Of course, we can't change a constant variable! Let's change this before someone notices 😁

```js
export let $counter = 0;
```

Now that's better 😊! Test again... nooo! This error again? May be restarting our server will change something but... it's useless. JavaScript is driving me crazy 😵‍💫

Let's do some Googling stuff ⌨️ ... owww 😲! It's been said that `every imported variable is immutable` which means that we cannot change it.

So, what's going on here? It seems that as soon as we are importing a variable from somewhere, that same variable is immutable. 🤔 Didn't know that. But as a great developer you can find some solution to that problem.

### 3- Attempt 2 - Proxy

Okay, first attempt didn't work. Let's try to use [proxies](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to help us deal with this assignation error.

Hum? Proxy? Well, a *Proxy* object makes it possible to create an intermediary for another object and which can intercept and redefine certain fundamental operations for it.

Enough chit-chat, let's get back to code! Let's change our _state.js_ file to this:

```js
import { setupCounter } from "./counter";

const handler = {
  set: function (obj, prop, value) {
    obj[prop] = value;
    setupCounter(document.getElementById("counter"));
    return true;
  },
};

export const state = new Proxy(
  {
    counter: 0,
  },
  handler
);

export const actions = {
  increment: () => {
    state.counter++;
  },
  decrement: () => {
    state.counter--;
  },
};
```

And now, let's adapt our _main.js_ file to match these changes:

```js
import "./style.css";
import javascriptLogo from "./javascript.svg";
import { setupCounter } from "./counter.js";
import { Button } from "./button";
import { actions, state } from "./state";

const App = () => {
  return `<div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
      <img src="${javascriptLogo}" class="logo vanilla" alt="JavaScript logo" />
    </a>
    <h1>Hello Vite!</h1>
    <div class="card">
      <p id="counter"></p>
    </div>
    <div id="buttons"></div> 
  </div>`;
};

document.querySelector("#app").innerHTML = App();

setupCounter(document.getElementById("counter"));
Button("#buttons", "-", actions.decrement);
Button("#buttons", "+", actions.increment);
```

Slight change to App declaration to match our beloved React app structure 😅
Let's see if it works... and ... it WORKS! 🎉

That's it folks! We've implemented state management from scratch using JavaScript.

There is a package developed around this _proxy_ concept called [Valtio](https://github.com/pmndrs/valtio) which is available for JavaScript, React, and so on... feel free to check and star it on Github.

## Conclusion

This was not a very shiny demonstration of the _proxy_ potential as what we did is just incrementing and decrementing a counter, but if you want a more advanced use of it, here is a [link](https://github.com/Lynxgsm/todoproxy) to a todo project using the same concept.

Cheers! 🍻🍻🍻