That's Groce!

October 22, 2020
0 comments Web development, Mobile, Preact, That's Groce!, Firebase

tl;dr That's Groce! is: A mobile web app to help families do grocery shopping and meal planning. Developed out of necessity by a family (Peter and Ashley) and used daily in their home.

Hopefully, the About page explains what it does.

Sample list
Screenshot of a sample list

The backstory

We used to use Wunderlist, but that stopped working. Next, we tried Cozi and that worked for a while but it was buggy and annoying in so many ways. Finally, we gave up and decided to build our own. Exactly how we need it to be, as efficient as possible.

We also tried a couple of regular to-do list apps where you can have shared accounts but we wanted something perfectly tailored towards the specific needs of family grocery shopping (and meal planning). That's how That's Groce! was born.

The killer features

The about page does a good job of listing the killer features but let's emphasize it one more time.

  • Free and simple.
  • Is really smart about suggestions and auto-complete for speedy entry.
  • Works offline (our usual grocery store has no reception unless you brave onto their WiFi).
  • Is real-time, so every other device updates immediately as you update or add something on your device.
  • Shared list; you can have your own list but you can invite co-owners.
  • You can sort your grocery list in the same order you usually walk through your grocery store.

It's not an app store app

Saved as Home screen app

You won't find it on the Apple App store. It's a web app that's been tailored to work well in mobile web browsers (iOS Safari) and you can use the "Add to Home screen" so it looks and acts like a regular app.
It would be nice to try to make it a regular native mobile app but that takes significant time which is hard to find but certainly something to aspire to if it can be done in a nice way.

"Really smart about suggestions"

What does that killer feature mean? (At the time of writing (Oct 2020), it isn't launched yet but the pieces are coming together.) Are there certain stables you buy recurringly? Like milk or bananas or Cheerios. If the app can start to see a pattern of commonly added items, it can suggest it immediately so when you're making your list on Monday morning, you just need to tap to add those.

Another important thing is that as you type, it can suggest many things based on the first or couple of characters you type in, but you can't suggest every single possible word so which one should you suggest first?
The way That's Groce! works is that it learns based on the number of times and how recently you add something to your list. As of today, look what happens when I type a on my list:

Suggestions based on typing 'a'
When I type a it suggests things that start with "A" but based on frequency.

The more you use it, the better the suggestions get.

Also, to get you started, over 100 items are preloaded as good suggestions but that's just to get you up and running. Once your family starts to use it, your own suggestions get better and better over time.

"Same order you usually walk through your grocery store"

This was important to us because we found we walk through the aisles in pretty much the same way. Every time. When you walk in you have your produce (veggies first, then fruit, then salad stuff) on the right. Then baked goods and deli. Then meats and alcohol. Etc. So if you can group your items based on these descriptions you can be really efficient with your list and it becomes a lot easier to cross off sections of the store and not have to scroll up and down or having to walk back to pick up that pizza dough all the way back at the deli section.

For this to work, you need to type in groups for your items. But you can call them whatever you like. If you want to type "Aisle 1", "Aisle 2", "Dairy stuff" you can. It's all up to you. Keep in mind that it might feel like a bit of up-front work at first, and it is, but your list is learning so you essentially only have to do it once.

Don't be a slave to your list!

If you do decide to try it, keep one thing in mind: You're in control. You don't need to type in perfect descriptions, amounts, groups, and quantities. If you don't know how to spell "bee-ar-naise sauce", don't worry about it. It's your list. You can type whatever you want or need. A lot of to-do lists invite you with complex options to organize the hell out of your list items. Don't do that. Think of That's Groce! as a fridge post-it note that you and your partner keep in their pocket that automatically synchronizes.

You can help

We built this for ourselves but it's built in a way that any family can use it and hopefully also be better organized. But once you sign in you can submit feedback for suggestions. And if you're into coding, the whole app is Open Source so it's fairly easy to modify the code or even host it yourself if you wanted to: https://github.com/peterbe/groce/

Also, if you do try it and like it, please consider going to the Share the ❤️ page and, you know, share it with friends. Much appreciated!

Lazy-load Firebase Firestore and Firebase Authentication in Preact

September 2, 2020
0 comments Web development, Web Performance, JavaScript, Preact

I'm working on a Firebase app called That's Groce! based on preact-cli, with TypeScript, and I wanted to see how it appears with or without Firestore and Authenticated lazy-loaded.

In the root, there's an app.tsx that used look like this:


import { FunctionalComponent, h } from "preact";
import { useState, useEffect } from "preact/hooks";

import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";

import { firebaseConfig } from "./firebaseconfig";

const app = firebase.initializeApp(firebaseConfig);

const App: FunctionalComponent = () => {
  const [auth, setAuth] = useState<firebase.auth.Auth | null>(null);
  const [db, setDB] = useState<firebase.firestore.Firestore | null>(null);

  useEffect(() => {
    const appAuth = app.auth();
    setAuth(appAuth);
    appAuth.onAuthStateChanged(authStateChanged);

    const db = firebase.firestore();
    setDB(db);
  }, []);

...

While this works, it does make a really large bundle when both firebase/firestore and firebase/auth imported in the main bundle. In fact, it looks like this:

▶ ls -lh build/*.esm.js
-rw-r--r--  1 peterbe  staff   510K Sep  1 14:13 build/bundle.0438b.esm.js
-rw-r--r--  1 peterbe  staff   5.0K Sep  1 14:13 build/polyfills.532e0.esm.js

510K is pretty hefty to have to ask the client to download immediately. It's loaded like this (in build/index.html):


<script crossorigin="anonymous" src="/bundle.0438b.esm.js" type="module"></script>
<script nomodule src="/polyfills.694cb.js"></script>
<script nomodule defer="defer" src="/bundle.a4a8b.js"></script>

To lazy-load this

To lazy-load the firebase/firestore and firebase/auth you do this instead:


...

const App: FunctionalComponent = () => {
  const [auth, setAuth] = useState<firebase.auth.Auth | null>(null);
  const [db, setDB] = useState<firebase.firestore.Firestore | null>(null);

  useEffect(() => {
    import("firebase/auth")
      .then(() => {
        const appAuth = app.auth();
        setAuth(appAuth);
        appAuth.onAuthStateChanged(authStateChanged);
      })
      .catch((error) => {
        console.error("Unable to lazy-load firebase/auth:", error);
      });

    import("firebase/firestore")
      .then(() => {
        const db = firebase.firestore();
        setDB(db);
      })
      .catch((error) => {
        console.error("Unable to lazy-load firebase/firestore:", error);
      });
  }, []);

...

Now it looks like this instead:

▶ ls -lh build/*.esm.js
-rw-r--r--  1 peterbe  staff   173K Sep  1 14:24 build/11.chunk.b8684.esm.js
-rw-r--r--  1 peterbe  staff   282K Sep  1 14:24 build/12.chunk.3c1c4.esm.js
-rw-r--r--  1 peterbe  staff    56K Sep  1 14:24 build/bundle.7225c.esm.js
-rw-r--r--  1 peterbe  staff   5.0K Sep  1 14:24 build/polyfills.532e0.esm.js

The total sum of all (relevant) .esm.js files is the same (minus a difference of 430 bytes).

But what does it really look like? The app is already based around that


const [db, setDB] = useState<firebase.firestore.Firestore | null>(null);

so it knows to wait until db is truthy and it displays a <Loading/> component until it's ready.

To test how it loads I used the Chrome Performance devtools with or without the lazy-loading and it's fairly self-explanatory:

Before
Before, no lazy-loading

After
After, with lazy-loading

Clearly, the lazy-loaded has a nicer pattern in that it breaks up the work by the main thread.

Conclusion

It's fairly simple to do and it works. The main bundle becomes lighter and allows the browser to start rendering the Preact component sooner. But it's not entirely obvious if it's that much better. The same amount of JavaScript needs to downloaded and parsed no matter what. It's clearly working as a pattern but it's still pretty hard to judge if it's worth it. Now there's more "swapping".

And the whole page is server-side rendered anyway so in terms of immediate first-render it's probably the same. Hopefully, HTTP2 loading does the right thing but it's not yet entirely clear if the complete benefit is there. I certainly hope that this can improve the "Total Blocking Time" and "Time to Interactive".

The other important thing is that not all imports from firebase/* work in Node because they depend on window. It works for firebase/firestore and firestore/auth but not for firestore/analytics and firestore/performance. Now, I can add those lazy-loaded in the client and have the page rendered in Node for that initial build/index.html.

Previous page
Next page