Ideas on small UX issue with sudden html changes on LiveView reconnect

Background is that I’ve a personal LiveView app that we use to keep our family shopping list on. When we notice that we’re running low on something we add it to the list of things to get, then whoever is shopping can open the list on their phone and mark things as “got” as they go round the supermarket.

The problem I’ve hit a few times is that I get my phone out to record I’ve picked up some milk, or something, without noticing that it’s reconnecting. As my thumb is about to tick the milk, it reconnects. Being a live list, someone has added a few things to it - the list shifts and I accidentally mark the wrong item.

It’s not the worst problem, but anyone got ideas on what I could do to mitigate the problem?

This is a bit of a can of worms, we run into things like this a fair bit at CargoSense, and I think it really has more to do with “what is the right UX here?” followed by “what is the best way to do that in LiveView?”.

As far as the UX goes, we’re becoming big fans of live updating individual items on a list, but instead of updating the list itself, displaying one of those “this page has updates, click here to pull them” or “drag to pull updates” style header item. This avoids shifting items out from under people, while still providing live updates to individual items.

Implementing this is a bit fiddly. You need to stash the ids of the items on the page somewhere, and you have to build looking for those into your state recovery flow. The easiest option is the URL params because then you get it in handle_params in the initial reconnect render, and you can just do all of your logic there. This is pretty ugly though. You can do it with forms, but then you tend to double fetch things (the initial list, and then you through it out on handle_event(“cached-id-list”)).

It’s a tough problem! It gets like 500 times harder if you have complex filtering on the page and you need to figure out whether changes happening on the backend should or shouldn’t prompt you to add items to the list, but that’s sort of a distinct topic.

After a great conversation with @mcrumm, who came up with a bunch of counter examples to why my idea also has pitfalls, the most reliable conclusion we arrived at for the actual reconnect case is some sort loading overlay that prevents you from interacting until the reconnect happens.

The issue with my idea is that even if you just reload the entities, the inner state like “checked” may have changed which could reposition it on the page or do any number of other things that makes it jump around.

I still think that for updating connected pages that the “there are X updates, click here to get them” pattern is good to avoid disrupting people’s actions. For reconnecting though it just isn’t feasible to allow actions prior to checking in with the state on the server.

That is the best approach if you don’t want to be intrusive and the decision-maker as the application rather than the user. As in good UX, the user is the decision-maker, the machine - application - is the dumb robot that does the task.

Ouf of sync items becomes a whole bag of nightmare with mobile phones suspended browser states. I ate my fair amount of dirt doing a mobile chat, matching shenanigans.

There is no easy fix to this but a compromise, unfortunately.

Since it is a self-use application and you might not want to introduce X changes pending or event syncing with client (local indexdb, server with timestamps) or prevent any UI state changes from the client with WebSocket connection lost and gained state tracking - PWA states.

May I simply recommend?

  1. view sends current timestamp with the list or any operations as tick
  2. next action from the user, receives this tick as an extra parameter
  3. check the timestampp diff to a - reasonable - value range
  4. Execute the intent or warn the user about stable data, send fresh data or do other nicer things to recover from lost intent

Thanks @benwilson512 and @cenotaph - those are excellent points. I do like the “you have no data” pattern. As @mcrumm points out it’s not great for this reconnect case.

I am not sure the timestamp idea really works - the issue is not the I am ticking “milk” with the old timestamp but that I’m accidentally ticking “bananas”, which would have a fresh timestamp, as the item has suddenly appeared as my thumb descended on the screen. Unless we disregard / warn on any actions within (say) 1 second of the screen reconnecting :thinking:

Thanks for the ideas - I will have have a play around and report back with the results.

One trick you can do here is to render the todo-list inside a form and have each todo-item-id as a hidden input:

<input name="order[]" value="13" />

Now when you reconnect, it will resubmit the form and you have your original ordering. If there are any new items, you can append them instead of shuffling the order. Of course this comes with the question if ordering is synchronized between the different clients (or if it is largely irrelevant).

3 Likes

I’ve had to deal with the same issue on mobile and essentially ended up doing both.

  1. Have a modal overlay when re-connecting on phx-error class.
  2. Use Form bindings — Phoenix LiveView v0.15.4 with some hidden fields.

Thanks all for the great suggestions. In this case I’ve decided on a super-simple overlay controlled with some hacky javascript.

CSS:

#disconnected-overlay {
    background: rgb(20, 1, 44);
    position: fixed;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    opacity: 0.8;
}

Element on the list page:

<div id="disconnected-overlay" phx-hook="DisconnectedOverlayHook" phx-update="ignore"></div>

And a Javascript hook:

Hooks.DisconnectedOverlayHook = {
    mounted() {
        this.hideOverlay();
    },

    reconnected() {
        this.hideOverlay();
    },

    disconnected() {
        this.showOverlay();
    },

    hideOverlay() {
        this.el.style.display = 'none';
    },

    showOverlay() {
        this.el.style.display = '';
    }
};

It provides a really clear visual clue that I should not trying to ticking any items until it’s reloaded, as well as preventing any changes happening. I did think about adding a 1/2 second delay on hiding the overlay but I don’t think it’s necessary - I’ll see how it goes.

I did initially forgot about the phx-upate="ignore", and got really confused by nothing working with the hooks.

Things I’ll bear in mind in future (different) cases are

  • “Content has changed - reload” functionality. Not too relevant here: someone could add an item just as I try and mark another as completed; it’s unlikely with a user base of 4 people, though :grinning_face_with_smiling_eyes:
  • Using the hidden fields form to send the current state on reconnect, and only add new items to the end of the list to avoid jumping around. In this case I don’t think I need it, and the order of items is significant as they are grouped by category in the order that we go round our local supermarket. (Veg, chilled, ambient, drinks).
3 Likes