DEV Community

Cover image for Using RxJS/BLoC as a state management solution for React
Karim Elghamry
Karim Elghamry

Posted on

Using RxJS/BLoC as a state management solution for React

Hello fellow programmers ๐Ÿ‘‹. in this article, we will discuss how we would use the RxJS library to implement the BLoC pattern as a state management solution for a React project.

Table of Contents

Prerequisite knowledge

Before going through the content of this article, it is assumed that the reader has prior basic knowledge in Rx, at least with observables and observers. The article will try to briefly go over these concepts, if needed.

What is BLoC?

BLoC, which stands for Business Logic Component, is a pattern created by Paolo Soares and Cong Hui from Google that aims to separate the business logic from the view in a given application. One of its primary goals is to make the business logic reusable and independent from the corresponding view. Initially, this pattern was introduced within the Flutter community, but throughout this article, we will take the concepts of BLoC and implement them in a React application. To start with, BLoC has these main rules:

  1. Each BLoC should have a dispose() method to cleanup any ongoing streams.

  2. Any data coming in a BLoC or any change in state within a BLoC must be done through a method.

  3. Any data coming out of a BLoC should be done through a stream/observable.

The following diagram illustrates how the view and a BLoC interacts with each other.

BLoC view

Creating a Todo app using BLoC/RxJS

Now that we've gone briefly over the concepts of the BLoC pattern, we will attempt to implement it in an example todo react application.

App initialization

First, lets initialize a react typescript project using vite:

npm create vite@latest todo-bloc --template react-ts
Enter fullscreen mode Exit fullscreen mode

this should create a folder called todo-bloc. Navigate to that folder and install the dependencies:

cd todo-bloc && npm install
Enter fullscreen mode Exit fullscreen mode

once done, we'll proceed by installing the RxJS package and the uuid package (we'll need it later):

npm install rxjs uuid
Enter fullscreen mode Exit fullscreen mode

we can now run the app in dev mode:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Folder structure

In our simple todo app, we will create the following sub directories inside the src directory:

src/
 โ”œโ”€โ”€โ”€blocs/
 โ”œโ”€โ”€โ”€components/
 โ”œโ”€โ”€โ”€hooks/
 โ””โ”€โ”€โ”€models/
Enter fullscreen mode Exit fullscreen mode
  • blocs: will encapsulate all of the reusable business logic components in our app.

  • components: will govern the view (UI) react components.

  • hooks: will hold all of our custom hooks.

  • models: will contains the interfaces and abstract classes that are shared across the app.

Creating the models

To simulate a real world application, we will start first by creating a Todo interface, which all of the todo objects in our app will conform to. We'll create a Todo.ts file under the models directory and add the following interface:

code

  • id: to distinguish between different todo objects.
  • text: the actual content of the todo object.
  • isDone: to keep track of whether this specific todo is marked as done or not.

Secondly, we'll create the base bloc interface which will include all common methods/properties shared between the BLoC's in our app. Ultimately, we'll only need a dispose() method on the bloc interface as previously mentioned. We'll proceed by creating a Bloc.ts file under the models directory and adding the following interface:

bloc

  • dispose(): a method invoked to close any opened streams and complete all observables within our BLoC.

Creating the BLoC's

Now that we have the interfaces in place, lets start by defining our business requirements for our app. Essentially, our app should:

  • display todo items chronologically.
  • provide a way to add a todo item.
  • provide a way to mark todo items as finished.

First off, we'll create a TodoBloc class that will implement the Bloc interface that was previously imposed. Ultimately, this class will live in a file called TodoBloc.ts under the blocs directory. To implement the first business requirement, we'll have to find a way to keep track of the current list of todo items. This is where Subject from RxJS comes into play. To recall, any data coming out of a BLoC should be through a stream/observable. To achieve that, we will create a BehaviourSubject that will hold the most recent list of todos, and emit new items whenever the state changes:

todo

  • _todos$: is a private property of type BehaviorSubject<Todo[]> that will be responsible for holding our Todo objects.

  • dispose(): we close/cleanup the _todos$ subject in the previously defined dispose method.

you might now be wondering, how are we going to expose the _todos$ subject to the view if the field is marked as private? good question. This is a bit tricky, because we don't want to expose the whole Subject to the view as we don't want the view to be emitting data directly to the Subject (we have to do it through a method as previously mentioned). Instead, we will create a getter for the _todos$ field that will only expose the Observable side of our subject:

getter

To cover the second business requirement, we will create a method called addTodo, which will be responsible for taking in a Todo object and adding it to the _todos$ subject:

addTodo

Explanation: the previous method accepts a Todo object as a param, gets the current list of todos from the _todos$ subject, concatenates the given todo to the list of todos, and finally pushes the new list of todos to the _todos$ subject.

To implement the final business requirement, we will add a toggleTodo method, which will be responsible for marking the given Todo item as done/undone:

toggle

Explanation: toggleTodo accepts a Todo object as a param, gets the current list of todos from the _todos$ subject, loops over the retrieved list and toggles the given Todo item by id, and finally pushes the new todo list to the _todo$ subject.

The final TodoBloc class should look like this:

Todo Bloc

Creating the hook

Now that we're done implementing our business logic, we need to start thinking of a way to connect our BLoC to the anticipated view in our app. We can facilitate this process by creating a custom hook that will have the following responsibilities:

  • accept an Observable of a generic type as a param.

  • consume the values coming from the Observable by subscribing to it when the components mounts.

  • preserve the state of the most recent value from the Observable and update the state each time a new value comes in.

  • cleanup the subscription when the component unmounts.

Having these points in mind, we'll create a useSubscribe hook under the hooks directory to fulfill these responsibilities:

hook

Explanation: useSubscribe accepts an Observable of a generic type as a param. A state is created to store the most recent value emitted from the given observable using the useState hook. We then use the useEffect hook to subscribe to the given observable on component mount and update the state with the most recent value. We also add a cleanup callback that will unsubscribe from the observable once the components unmounts. Finally, we return a copy of the state to the view to be able to effectively use it in display.

Creating the view

Now that we have everything in place, let us create the view to demonstrate how we can tie these all pieces together. First, we create a simple TodoItem component under our components directory to display a single todo item:

todo view

Explanation: the TodoItem component accepts two params - todo, which is the given Todo object, and onToggle callback, which will be triggered when the user marks the items as done/undone. The component displays the text property of the given todo in a li tag, and style is rendered according to the isDone property.

Finally, in our App.tsx file, we will tie all the pieces together:

view

Explanation: we create an instance of the TodoBloc outside of our App component. We then consume the most recent value from the the TodoBloc by using the previously defined useSubscribe hook and passing the todos$ observable as a param. Now, the todo variable will always hold the most recent list of todos, and will update accordingly. To give the user the ability to add new todo items, we create a simple form with a single controlled input element and a submit button. On submit, the addTodo method on the TodoBloc will be invoked, and a new Todo object will be pushed to the list of todos. Finally, to display the list of todo items, we map over the todos list and render a TodoItem component for each given todo. Each TodoItem will be provided an onToggle callback that will invoke the toggleTodo method on the TodoBloc for each item, respectively.

The final result will look/behave as follows:

demo

Considerations and closing remarks

As your application grows, your number of BLoC's will grow respectively and you'll need to find a way to manage them cleanly. Multiple view components may consume a single BLoC and you'll need to find a way to provide it to these components. One way to go about that is to use the context api to provide a single bloc to multiple components. You may also want to look into the Singleton design pattern to preserve a single copy of a given BLoC within your app. You would also want to manage the disposal of each BLoC when it is no longer needed in your app (in our todo app, we did not call dispose() anywhere because the TodoBloc has the same lifespan as our application). Nevertheless, be flexible and reasonable with your design and do not over engineer your solutions. Happy coding ๐ŸŽ‰!

Top comments (1)

Collapse
 
hossamelghamry profile image
Hossam Elghamry

A must-read for any reactive programming enthusiast๐Ÿ”ฅ