Frontend state management with clean architecture
Tags: typescript, cleancode, beginners, learning
Published: 2023-08-05 07:15:26

1. 
## **INTRODUCTION**

One of the most critical aspects of developing a client-side application is how we handle global state management.

Many developers spend hours and hours searching for the perfect tool: Redux, Akita, Pinia, Ngrx, Context, Vuex... But the truth is, if we aim for code that is agnostic and truly maintainable over time, we cannot simply delegate the core piece of our applications to external libraries or tools whose future maintenance is uncertain.

That's where clean architecture comes to the rescue, advising us to separate the domain or core components of our application from those parts that are likely to change over time. In other words, tools and frameworks should never directly manage any piece of the global state of our application.

So, I've taken the initiative to create this post and share my step-by-step solution on how to handle global domain state in a client-side application using Typescript and Object-Oriented Programming (OOP). 

Let's begin! 🚀

## **REACTIVITY**

When it comes to managing global state, we need it to be reactive to any changes that occur within it. Therefore, if we are going to build a global state handler, reactivity should be our top priority.

1. Let's create an abstract class that will solely take care of managing the global state with reactivity:

```
export abstract class UseCase<S> {
    public state: S;
    private listeners: Subscription<S>[] = [];
}
```
We will name the class **_UseCase_**, and it will have two properties: **_state_** and _**listeners**_.

state will be responsible for holding the global state, while listeners will handle reactivity.

2. Our class will include a **_constructor_** that receives the state of the feature extending the use case:

```
    constructor(initialState: S) {
        this.state = initialState;
    }
```

3. Methods:

The first method of this class will be the one that allows us to execute any use case:

```
abstract execute(value?: any): void
```

Absolutely right! The first method should be abstract so that the extending class can handle any logic, such as calling an API to fetch information.

On the other hand, we also need a method to retrieve the state when required:

```
    getState(): S {
        return this.state;
    }
```

Exactly! We need another method to update the state. This will allow us to modify the global state when necessary:

```
    updateState(newState: Partial<S>): void {
        this.state = {
            ...this.state,
            ...newState,
        };
    }
```

Now, here comes the magic where reactivity occurs intuitively and easy to understand.

We'll add a small piece of code to the **_updateState_** method that will execute any method found in the **_listeners_** property we created earlier. 

Here's how the **_updateState_** method would look like:

```
    updateState(newState: Partial<S>): void {
        this.state = {
            ...this.state,
            ...newState,
        };

        if (this.listeners.length) {
            this.listeners.forEach((listener) => listener(this.state));
        }
    }
```

Now, every time we want to update the state, it will trigger a function to which we'll pass the updated state as an argument. 

This will enable **_reactivity_** as any listeners registered with the listeners property will be notified and can react accordingly to the changes in the **_global state_**. This approach simplifies the handling of reactivity and makes it intuitive and easy to manage:

```
listener(this.state)
```

To make the listeners property functional, we need to introduce the methods we want it to execute. We'll achieve this by adding another method called **_subscribe_**.

```
   subscribe(listener: Subscription<S>) {
        this.listeners.push(listener);
    }
```

The subscribe method will allow us to add methods to the listeners property so that they can be executed when the state is updated. This way, we can dynamically register functions to react to state changes in our application.

Shall you guess which method we could pass to this **_subscribe_** method? 😉

## **CUSTOM HOOK**

Exactly! Now that we have our abstract class, we can use it anywhere in our code. But how do we connect our fancy framework to our previous code? 

Well, there can be numerous solutions from here on, and it all depends on the specific framework we're using.

For this example, let's dive into React and create a **_custom hook_**! Why not make it fun and enjoy the magic of connecting everything smoothly?: 

```
export default function useSubscription<S, U extends UseCase<S>>(useCase: U, setStateFn: Dispatch<SetStateAction<S>>) {
    useCase.execute();
    useCase.subscribe((state) => setStateFn((prevState) => ({ ...prevState, ...state })));
}
```

The **_useSubscription_** custom hook allows us to call the execute method, which can, for instance, make API calls, and we can also use the subscribe method to pass a function that updates the state of our components: 

```
setStateFn((prevState) => ({ ...prevState, ...state }))
```

With this magical **_useSubscription_** custom hook, we can seamlessly connect our abstract class to React components and enjoy the wonders of reactivity! Now, every state update will trigger the registered functions, ensuring our components stay in sync with the global state. Isn't this a delightful way to handle state management? Let the fun begin!. 🎉

## **IMPLEMENTATION**

Now that we've reached this point, let's see how we implement this custom hook in our **_React_** component.

Imagine an application for recipes, and our component wants to fetch recipes from the database. With React, we'd do something similar to this:

```
const [listRecipes, setListRecipes] = useState<ListRecipesUseCaseState>(listRecipesUseCase.getState())
```

We'll use useState to manage the state of recipe lists, and we'll initiate it by calling the API with the method: 

```
listRecipesUseCase.getState()
```

Once again, you got it! Following this, let's use **_useEffect_** to subscribe to our global state:

```
 useEffect(() => {
        useSubscription<ListRecipesUseCaseState, ListRecipesUseCase>(listRecipesUseCase, setListRecipes);
    }, []);
```

With the **_useEffect_**, our component will subscribe to the global state changes, ensuring it stays in sync with any updates that occur. Now, our recipe app is all set to whip up a delightful user experience!😋

## **SVELTE EXAMPLE**

Fantastic! To wrap it up, let's illustrate the same implementation, this time using **_Svelte_**:

In Svelte, we'll create a **_store_** that exposes a method called **_setAppState_**, which will update the state using the **_appState.set(state)_** function. :

```
export const appState = writable(stateUseCase.state, () =>
  stateUseCase.setDefaultState(books)
)

export const setAppState = (state: GlobalState) => {
  appState.set(state)
}
```

To use this approach, we'll call the **_subscribe_** method in our **_App.svelte_** file.

Here's how it would look:

```
beforeUpdate(() => {
    stateUseCase.subscribe(setAppState)
  })
```

This way, our app will automatically subscribe and update the global state every time we call **_updateState_** in our use cases. It creates a seamless and **_reactive experience_**, keeping our app in sync with the **_global state_** effortlessly

Thank you for joining me on this journey! I hope this has been helpful, and I look forward to your feedback.

Happy coding!

@oscarprdev 🚀🌟