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.
- Prerequisite knowledge
- What is BLoC?
- Creating a Todo app using BLoC/RxJS
- Considerations and closing remarks
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.
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:
Each BLoC should have a
dispose()method to cleanup any ongoing streams.
Any data coming in a BLoC or any change in state within a BLoC must be done through a method.
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.
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.
First, lets initialize a react typescript project using
npm create vite@latest todo-bloc --template react-ts
this should create a folder called
todo-bloc. Navigate to that folder and install the dependencies:
cd todo-bloc && npm install
npm install rxjs uuid
we can now run the app in dev mode:
npm run dev
In our simple todo app, we will create the following sub directories inside the
src/ ├───blocs/ ├───components/ ├───hooks/ └───models/
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.
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:
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:
dispose(): a method invoked to close any opened streams and complete all observables within our BLoC.
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
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:
_todos$: is a private property of type
BehaviorSubject<Todo>that will be responsible for holding our
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:
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
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
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:
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
TodoBloc class should look like this:
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:
Observableof a generic type as a param.
consume the values coming from the
Observableby subscribing to it when the components mounts.
preserve the state of the most recent value from the
Observableand 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:
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.
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:
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
Finally, in our
App.tsx file, we will tie all the pieces together:
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:
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 🎉!