Let’s quickly uncover a few simple tips that are quick to implement and have a big impact.
Do not recreate navigation, update it
The most common error I see across the Wave apps is ugly navigation that seems to be laggy.
The reason for this behavior is that we want to save the clicked
value and set it explicitly. The explicit setting is not a problem on its own, the problem is if we recreate the card (assign
q.page['key'] a new value).
Whereas the fix is straightforward — create the navigation once, and update its value afterward.
Giving us the nice, smooth, and fast result we wanted.
Understand H2O Wave state scopes
One of the common coding errors when developing an app I see nowadays is using an incorrect type of state. This can cause both performance and UX problems. Please see the docs to get more info.
Typical performance problem — unnecessary file loading. Let’s say we have a scenario where we want to load a dataset file for people to download from the app later on. There is going to be only a single dataset for everyone. The typical code for this looks like this:
Can you spot the problem here? The code above uploads the same file on every single browser connection (browser tab). That means that if 200 people visited our app, the very same file would be uploaded 200 times which is wasteful for both network bandwidth and disk storage. Moreover, this code makes every single user wait until the file is uploaded and only after then the UI is painted. The solution? Simply use
q.app instead of
q.client for these cases. Note that using
q.app means that only the very first user needs to wait for the file to be uploaded.
However, sometimes you do not want to bother even the first app visitor. For these cases, one can use
on_shutdown hooks. These are just simple functions that run when your app gets started (prior to being available in a web browser) or is about to be turned off. The downside is that you do not have access to the
q object since no UI is present at this stage. Can be useful for running some preparation scripts, training a model, cleanup logic, etc. For more info, see the docs.
Back to our state scopes discussion. We saw a potential performance problem. However, using an incorrect state can be problematic on the UX level as well. Let’s imagine we need to spawn a process that emits periodic updates to our app. If we put the process identifier into a
q.client, the user can open a new tab and start another process which might be undesirable (he already has one going on for his account). Using
q.user solves this problem painlessly as it ensures all browser tabs of a single user are synced properly.
You don’t always need to upload all your files
We have already seen the proper way to upload global files that are user-independent — upload them only once. However, the
site.upload is more suited for dynamic cases when you do not know what files you want to provide your users with in advance. On the other hand, if you already have the dataset ready, there is a faster alternative.
The solution is to either use
private-dir depending on what kind of access you want to provide. The 2 are Wave server parameters that allow you to choose a directory that will serve files directly from the wave server. Since most of the time both the Wave app and Wave server is located on the same machine, they also have a common file system.
This also comes in handy when you use custom JS or CSS to tweak your app.
Give your machine a break
Has your computer ever turned into a helicopter due to its fan being too loud? Well, one of the reasons might be Wave reload feature during development time. If you look closely at the terminal output when running your app, you might notice subtle info about files being watched.
By default, the python server that Wave uses watches your file system for changes and if it detects any, it simply reloads the whole app. This is very useful but can be troublesome if you have the main app file in a folder with other files that are not directly related to your app. In my case, that’s a virtual environment folder that tends to be quite heavy, adding a lot of extra work to the reloader process.
Instead, the solution is to create a separate
src folder or some people prefer a folder named like their app, e.g.
my-wave-app folder. Then move all the app-related files into it and execute
wave run command from within the folder itself.
Voila, we can enjoy our silent office again.
Do not write all the code yourself
The primary audience for H2O Wave are people working with Data Science and Machine Learning, thus not software engineers. For this reason, we want to do everything we can to make the whole app-building process as smooth and enjoyable as possible. So instead of writing your app from scratch, please go ahead and either download our VSCode extension or Jetbrains plugin, depending on your preferred IDE, which comes with plenty of predefined snippets like
w_app_* that offer various starter app templates. Using them gives you a head start since you already begin with a working app that has generic stuff like navigation already figured out so the only thing left is to add your own visualizations and workflows.
Even though this blog post was not a deep dive like the previous ones, I believe even these little things can have a significant impact on your day-to-day Wave development. In case you feel like there is a topic that you would like to know more about, please do not hesitate and start a discussion with your suggestions!