I started working with React few year ago, always the project creation was from scratch, not using any template/scaffolding. Regarding bundlers normally I used either Webpack at work or Parcel for personal projects.
Few months ago, I wanted to start a personal project to keep track of my travel expenses. I was in a kind of rush because at that time, I was almost in the middle of my gap year, I wanted to focus on implementing main functionality and get an MVP (minimum viable product) the sooner the better, so I thought it was the right time to try out Create React App or CRA.
CRA allows you to have a production ready PWA in React quickly, which is awesome. They take care of configuration and package dependencies, you only have to take care of dependencies you need for your project and of course, implement your project, CRA is good, but is not magic.
As I said, I wanted to be implementing business logic ASAP, so together with using CRA, I also took other decisions/shortcuts driven by the need of speeding up the development pace, I will talk about those choices in following sections describing some drawbacks and benefits.
Chosen Technology Stack for Budget Tracker ¶
So far I am quite happy with the outcome, but with the lessons learned while developing this app, in the future, with enough time, most likely I will not choose same technology stack again. You can try the application Budget Tracker and judge for yourself.
Along this post I will describe what are, in my experience, the benefits and drawbacks of taking these shortcuts and technical decisions.
- Chosen Technology Stack for Budget Tracker
- Create React Application: CRA
- UI Components Library: Material UI
- Charts library
- What next?
You might want to customize your Service Worker to send/receive post messages, to perform background sync or show web notifications. In that case, you will have to eject your project and maintain the configuration by yourself, which might imply a little bit of headache.
There are other options to customize service worker and avoid ejecting CRA, but they are not straightforward enough for my taste.
If you need to perform any heavy processing without blocking the main thread, you can just use a Web Worker, but this feature is not supported by CRA. The Web Worker can communicate with main thread using post messages and it can also show web push notifications.
There are also other options to use Web Workers in CRA and not eject, but they imply quite some extra work.
Webpack is the bundler used by CRA. You don't need to know much about it, unless you eject your project, then you will have to deal with Webpack configuration file, this is just a warning, just in case you are not comfortable with it.
Budget Tracker supports data synchronization between different devices, so it requires a backend side to deal with authentication and to save/read data remotely. I considered two options: Firebase or implement REST API.
But Firebase brings some drawbacks you must know before choosing it.
Happily Budget Tracker implementation is following code-splitting principle, so user experience was not really affected with this integration. But user's device will eventually have to download this extra 39% (539KB).
Offline first, not really¶
This section is not relevant if your use case doesn't imply saving data linked to the user.
Anonymous user + offline mode features will allow an application to work as offline first.
So... what is this "Offline first, not really" issue about? Let me explain a tricky scenario. First time the application is opened, Firebase needs to authenticate the user, to do so, user's device has to be connected to Internet, so you have to consider following scenario and either be OK with it or deal with it:
- PWA is installed in your device.
- User is not authenticated.
- User's device is offline.
- User opens the PWA and tries to save some data.
- That data won't be saved correctly, because there is no user to link the data with, not even an anonymous user, because application needs to call Firebase API to create an anonymous user.
This is not big deal, because it will seldom occur. If you want to deal with it anyway, check next section explaining how and why I did deal with this scenario.
How did I deal with this issue with Budget Tracker?¶
First of all, this might not be an issue for your use case, because it will happen only first time application is loaded. I just wanted Budget Tracker to be fully offline first, because it brings other benefits.
- Implement 2 persistence layers: Local (IndexedDB) and Remote (Firestore).
- Save always data locally, regardless user authentication status.
- If there is any authenticated user, after saving to local layer, propagate same action to remote layer (Firestore) asynchronously.
- If user is not authenticated, Budget Tracker won't load Firestore client bundle. As I explained before, it is 27% of application size.
- Application reads and writes are faster, because latest valid data is always saved locally.
- Clarification: Save data in Firestore is also fast, because data is also cached locally, but it does a little bit more than just saving to IndexedDB and you need an authenticated user.
You can find a more detailed performance report, where I analyze 3 different implementations:
- Only Firestore client.
- Local (IndexedDB) and remote (Firestore) persistence layers.
- Same as previous one, but remote layer implemented in service worker.
The performance results were in general better for option 2.
Firestore API is easy and intuitive, I really like it, but don't assume it will have same features as other document DBs or SQL DBs.
Consider other alternatives:
UI Components Library: Material UI ¶
I chose Material UI: "React components for faster and easier web development. Build your own design system, or start with Material Design", quoting their website.
There were two reasons which drove me to use Material UI:
- To create simple UI components which are accessible, responsive and with a consistent design is tricky and time consuming.
- It has SVG set of Material Icons. Budget Tracker allows to create categories defined by a name and a selectable icon, so this icon set was really convenient.
There are some drawbacks, not very important in my opinion, maybe the most annoying for me is the first one:
- Jest Snapshots + Material UI: The snapshots are generated with Material UI CSS class names, but CSS classes order might not be deterministic, so a test might pass in your local host but not in CI host. They are working on solve this issue, more info at github.
- Performance: There are some performance issues in Github. During last months, whilst I've been using this library, I can say they are working hard on fix them and bring new features.
- UI components libraries are complex and do quite a lot work, so most of them are quite heavy. Material UI bundle size weights: 304.2kB minified. You can find some recommendations to reduce bundle size at Material UI website.
Many of the chart libraries I've found are really powerful and complete, but they are also heavy because they depend on other third party libraries like D3.
After quick search in the Internet I discovered other lighter alternatives:
Both libraries come with more chart types than just bars and XY axis, take a quick look at their websites if you are interested about their supported chart set and to check how they look like.
I will try to come up with a conclusion better than: "It depends", "Your use case will tell you" and so on.
That said. It depends on your needs :p.
Seriously, let's play "do not go for ... if ...":
Do not go for CRA if¶
- You want to customize Service Worker for Background sync or showing push notifications.
- You want to use Web Workers.
Do not go for Firestore if¶
- You are aiming for your app to be hit by many users and you don't know the estimated amount of reads/writes, otherwise you might get surprised with the bill. Firestore scales like charm, maybe your budget doesn't.
- Bundle size is critical for your web application. Remember that bundle size is not that critical if you are implementing a PWA, because your app files are cached.
Do not go blindly for the best charting library¶
First of all, check what kind of charts you need. In many applications you are OK with XY axis chart, time series, bars or pie charts. You can easily get an smaller bundle size by just using a simple charting library like Frappe charts or Chartist
Just check what are your requirements, if you are not sure about them, the technology stack I used for Budget Tracker consists of awesome products which most likely will fit your use case.
My next technology stack bet goes for Svelte/Sapper, it is promising project, the results for small projects are really impressive, mainly in regards to bundle size, it is ridiculously small and development experience is quick and intuitive.