How to create Redux Store only with VanillaJS and RxJS

In this guide, we’re going to build Redux Store architecture using only VanillaJS and RxJS. Yes, we’ll NOT use React at all.

Before you attempt this guide, you should have:

  • Good knowledge in VanillaJS.
  • Basic knowledge in RxJS.
  • Basic knowledge in TypeScript, HTML, Scss.

In general, it’s not a good idea to re-create Redux (or redux-observable) by yourself. Redux is a highly rated public package and there are many benefits of using it, such as big community, troubleshooting and edge cases we probably won’t cover. This guide is more of an exercise to understand how it all works and a demonstration of what we can achieve using RxJS.

First, what is Redux Store?

According to redux.js Redux Store is “A predictable state container for JavaScript apps.”. Well, that’s pretty much it. Your application got some global state which holds everything that you need. You can read its data across your application, listen to changes, and mutate its content via so-called Actions, Reducers, and Epics. Actions are objects which describe what’s need to be changed in the state or what task we would like to perform, while Reducers are the implement of how to mutate the state in a synchronized way. But don’t be worry, everything will be explained in details later on. In general, Redux attempts to make state mutations more predictable by imposing certain restrictions on how and when updates can happen.

Benefits of using Redux Store architecture:

  • Single source of truth — data has only one official source to be used by data consumers.
  • Separation of concern — which lead to reusable pieces of code, and make changes much easier.
  • Low coupling & High cohesion — every component in this architecture has its own purpose and there is absolutely no coupling between each other. That's actually leading to Separation of concern.
  • High scalability — extending this architecture to a bigger scale is very easy.
  • Simple and clean — well that’s exactly what you will see after finish reading this guide.
  • Easy to debug

The drawbacks of Redux Store architecture:

  • Increased layering complexity in writing state manipulation logic like Actions and Reducers.
  • Not very useful for small apps.

Let’s start coding

By the end of this guide, we will achieve a fully functional Redux Store architecture implemented only with VanillaJS and RxJS. The application will have some basic user-interface that allows us to request some posts, echo them and delete every item. Again we will NOT use ReactJS.

Project files structure

- src/
- store/
- epics/
- delete-post.epic.ts
- get-posts.epic.ts
- index.ts
- reducers/
- delete-post-request.reducer.ts
- delete-post-success.reducer.ts
- get-posts-success.reducer.ts
- index.ts
- store.actions.ts
- store.epics.ts
- store.init.ts
- store.reducer.ts
- store.types.ts
- store.utils.ts
- index.js
- styles.scss
- utils.ts
- index.html
- package.json
- tsconfig.json

Before we start diving into the code, as I always recommend, let’s first define the types & interfaces that will be used throughout our application. Once we covered that, we will be able to speak the same language.

Types

/store/store.types.ts

[Interface] ApplicationState — In the ApplicationState Interface, we declare what data will be stored in our state. In this toy application, we will have a dictionary of posts where the key is the post id and its value is the actual instance. In addition, we will need another dictionary with the same key to a boolean value this time, which indicates that this post is currently being deleted (async action).

[Type] Reducer —AReducer specify how the application’s state changes in response to actions. Or in other words, given the previous application state and the dispatched action (which holds a type and some payload), the reducer will return the newly updated state in a synchronized way.

[Type] Epic Epic is a function which takes an Observable of actions and returns an Observable of actions. Actions in, actions out. They let us write async logic that interacts with the store.

[Type] ActionCreator ActionCreator is a function definition that can take any number of arguments and must return an Action. We will use action creators, well, as you guessed to create actions.

[Interface] Action — Action contain a type and a payload which is optional, so the Reducer will know what logic to run, and what data needs to be replaced.

[Type] Dispatch — The Dispatch function, very simple, just fires an action.

[Type] StoreSubscriber — That's the regular RxJS subscriber, just forcing a type of ApplicationState. For the curious of us, that’s the definition of NextObserver .

/** OBSERVER INTERFACES */

export interface NextObserver<T> {
closed?: boolean;
next: (value: T) => void;
error?: (err: any) => void;
complete?: () => void;
}
export class Observable<T> implements Subscribable<T> {
...
subscribe(
next?: (value: T) => void,
error?: (error: any) => void,
complete?: () => void) : Subscription;
...
}

That’s not really important… don’t waste more than a minute on that.

[Interface] Store — well yes, that's the Store, we need a way to dispatch actions, and to subscribe to its changes.

Utils

Here are some utility functions we are going to use.

/store/store.utils.ts
/utils.ts

Actions

This application as mentioned earlier will support 2 use-cases:

  1. The user will be able to get a list of 100 posts by a press of a button. Those will be printed to the screen.
  2. The user will be able to delete any post he wants.

So let’s review our actions file.

/store/store.actions.ts

In Redux we need to define 2 actions for every single async action we would like to perform (if we are dependent on its response of course) since the reducer is updating the state synchronously. The first will cause an Epic to perform some network request or async logic, and the second is used by the epic to fire the resolved data once the logic is done. We are handling async jobs in a synchronized way.

Reducers

Since we are already created the interface of the ApplicationState . We can start writing our reducers.

We will need 3 reducers:

  1. Get posts success reducer— will handle the update of getPostsSuccess action.
  2. Delete post request reducer — will handle the update of deletePostRequest action.
  3. Delete post success reducer — will handle the update of deletePostSuccess action.
/store/reducers/get-posts-success.reducer.ts
/store/reducers/delete-post-request.reducer.ts
/store/reducers/delete-post-success.reducer.ts
/store/reducers/index.ts

Creating the Root Reducer

The root reducer will define the initial application state, and for each dispatched action will execute the relevant reducer that we just created according to the type of the action. Otherwise, the previous application state will be returned.

Epics

As said earlier “actions in, actions out”. They let us write async logic that interacts with the store. The actions that will be returned from an epic will be automatically dispatched.

We will need 2 epics in our toy application:

  1. Get posts epic — will handle the async task of reaching the API to get an array of 100 posts.
  2. Delete post epic — will handle the async task of reaching the API to delete a single post.
/store/epics/get-all-posts.epic.ts
/store/epics/delete-post.epic.ts
/store/epics/index.ts

Combine the Epics into a single Observable

/store/store.epics.ts

Finally, createStore

This function creates the application store, set up our reducers, epics and an observable of actions.

/store/store.init.ts

Let’s walk through command by command:

  1. Create a Subject of Actions.
// Create the Action Subject
const action$ = new Subject<Action>();

2. Create the state and attach the reducer to the scan rxjs operator.

Each dispatched action will be reduced by the reducer.

const state$ = action$.pipe(
    startWith({ type: "@@INIT" }),
    scan(reducer, undefined) // <-- The magic happens here
);

scan works like Array.reduce but over time:

/**
Applies an accumulator function over the source Observable, and returns each intermediate result, with an optional seed value
**/
scan(accumulator: function, seed: any): Observable

You can see some examples here:

https://www.learnrxjs.io/operators/transformation/scan.html

3. Connect our Epics to the Store

Each epic will get the stream of actions and will return another action when the API call or any other async job will be done. The emitted actions will be immediately dispatched through the normal store.dispatch().

epics(action$, state$).subscribe(action$);

4. Return the Store

return {
    dispatch: action$.next.bind(action$),
    subscribe: state$.subscribe.bind(state$)
};

Our HTML

Simply a button to get all posts, and a placeholder to echo out the posts from the application state.

/index.html

The Main

Play with it

Conclusion

Well, that’s it, we have built Redux Store architecture using only VanillaJS and RxJS. In this article I wanted to show you the power of using RxJS, and what can we achieve using its tools.

This guide is heavily inspired by Redux-Observable. I use this package very often in large scale applications. If you liked this guide you should definitely take a look at this package.

Git: https://github.com/GuyChabra/redux-vanillajs-rxjs

Thank you for reading,

I would appreciate any comments, notes, and questions.