Behind the Scenes: We.Bloom

Alicia
6 min readDec 29, 2023

--

Tech Stack: React, TypeScript, Google Firebase Realtime Database
Others: React Bootstrap, Mapbox

Motivation

While enjoying a relaxing massage and chatting with the owner of a small home-based business, the topic of a dwindling customer base came up. Through this conversation, I realized that many small businesses struggle due to a lack of online presence mainly due to their lack of technical knowledge. For this, welcome, we.bloom.

we.bloom — a self-service website enabling business owners to showcase their products online without the need to understand the mechanisms behind it.

As a personal challenge to myself, I decided to take this opportunity to build the application using TypeScript; which I have documented my learnings here.

Technicalities

The initial plan was to create a plain static website with all data stored on the application itself. However, there were two concerns with this: poor efficiency, low maintainability. Thinking ahead, as the number of offerings grow and the product scales, this will cause the application to struggle with the increasing load, resulting in performance issues such as slower response times and higher resource utilization. Moreover, with this model, a code change will be needed for any changes, increasing the turnaround time.

With this in mind, I chose to use Google Firebase Realtime Database to handle the data involved with CRUD operations.

For this project, the main data I am working with are: categories, services and details.
Categories and services have a one to many relationship where each service falls under a single category while each category can hold multiple services.
Each service data holds an array of details objects.

Data Structure

Key Feature

To qualify as a self service platform, it is essential to have a UI which enables users to perform CRUD operations. Building on this, given that data are shared across multiple components, I made use of reducers and context providers to manage the shared state.

Create
New data can be created in Firebase by specifying the reference path and passing an input value into it.

set(ref(database, 'categories/' + formData.name), data);

In order to enforce the one to many relationship mentioned above, every service is tied to a category through the dropdownOption field which holds the list of all available categories retrieved from the category context provider. Each service is then saved under a path with the category as a key.

set(ref(database, 'services/' + dropdownOption), updatedData);
Creating a new service

Read
The read functionality comes into play when displaying the list of categories and services on the /home and /services page. To prevent fetching of data from Firebase on every render, a SET action is dispatched when the page first mounts to store the fetched data in the context providers. Subsequently, using an if condition, a request to Firebase will only be made if there are no data available in the context providers.

This serves two purposes: limits the number of calls to Firebase, effectively preserving the downloaded limit and improves user experience by decreasing the application loading time.

Update
For the update functionality, I made use of two variables — unique ID and modal context provider.

The ID is used against the existing state, helping to differentiate between a creating a new data and updating an existing one. If data matching the ID is found, existing data in Firebase can be overwritten by using the existing database reference path and passing in the updated data.

The modal context provider, amongst other functionalities, is responsible for determining if a modal containing a form pre-populated with retrieved values should be displayed or not.

Updating an existing service

Delete
Since every service is tied to a category, it makes sense for all related services to be removed if the associated category is deleted. The inverse is not true.

To achieve this, the category name is retrieved using the unique cateogry ID. Using the category name and passing null into the database reference path, it deletes all associated data on Firebase.

set(ref(database, 'categories/' + name), null);
set(ref(database, 'services/' + name), null);

For a good user experience, a modal will be shown seeking confirmation from the user before deletion happens.

Confirmation modal

Challenges & Learnings

1. Data structure

There are endless possibilities when it comes to deciding on a data structure. Here are some which I thought of:

  • Option 1: Nesting an array of services under each category
  • Option 2: Nesting categories under service array
  • Option 3: Extracting the details array of each service into its own database object

Ultimately, the decision was made based on the needs of my application. in the section below, I will break down why the above options are not viable.

Option 1
When fetching data with nested data structures, all of the child nodes are also retrieved. This serves little purpose for my application as each page is built to only display either category or service data.

Option 2
Even though this data structure will work, it seems inefficient given that we will have to loop through every child in the array and check for the corresponding category.

Option 3
While data nesting should be avoided, in context of my application, it is more efficient to nest the details data within the service data. This is because the associated details data will always be required when fetching the specific service data. By extracting the details out, not only does it make it more cumbersome when fetching all data related to a specific service, it also increases the number of calls to the database.

As such, I opted for a data structure where category and service data are saved in separate paths, allowing for separate calls to be made to retrieve the desired data, keeping the application responsive and fast.

2. State Management

I was building a functionality which enables users to perform CRUD operations on service details. The idea is to allow users to add as many additional fields all at once. However, the main difficulty faced was managing the state of the details data, struggling between reading from an array of details data and a single detail object from each service data. (It was also this issue that led me to think of Option 3 from above.)

To resolve this, I created another context provider dedicated to managing the details array instead of relying on its parent — the service context provider.

To simplify things further, I adopted an approach which only allows users to add a new field if all the other existing fields are populated. This prevents complexities such as identifying which of the multiple fields were changing and which duration-price pair were tied together all while maintaining the purpose of the component.

Future Enhancements

1. OTP Verification

As part of early development to prevent deletion of data, I have created a functionality which checks if the specific data is ‘protected’. If it is, verification is required before deletion goes through.

Validation form

Moving forward, I will be adopting the use of OTP for verification purposes where only admins are able to access the page for performing changes to data.

2. Improving user experience

Despite having accomplished the main purpose of the application, there can be confusion surrounding the purpose of each form input field. For instance, under the category data, the description field is used in the banner component while the name field is used in the card component.

To improve clarity, a possible approach can be to display the banner component and reflect the value change dynamically.

--

--