'Form handling using cache-and-network policy, how can I handle this case?

I'm using urql and Svelte for a new web app.

I ran into a logical problem that I can't figure out how to best solve.

Suppose I have many todos and the list of these todos is already cached on my phone browser.

While I'm on the subway (very slow network) I open one to edit it (then the app loads the edit form with the todo from the cache) but since I use the "cache-and-network" policy and shortly before my wife changed it the todo re-updates itself undoing all my changes that I was writing in the textarea.

How do you think this situation can be fixed?

  1. Should I wait for the cache-and-network to finish before I can edit the form? (This is not great for a maybe-offline UX);

  2. should I warn the user that there is an updated todo?

  3. should I warn the user that there is an updated todo only when I'm saving it in the DB in the backend?

  4. other suggestions?



Solution 1:[1]

You can use navigator.onLine to check if your user is online or offline. if he is offline, save his changes to localStorage. when the status changes again; you can compare your current state to localStorage and update the form accordingly.

most of the logic I'm talking about is explained here

if you need help with saving a form in localStorage, you can use this template:

[].forEach.call(document.querySelector('#form-id').elements, function(el) {
  localStorage.setItem(el.name, el.value);
});

// then refresh the page and run this to restore your form values:
[].forEach.call(document.querySelector('#form-id').elements, function(el) {
  el.value = localStorage.getItem(el.name);
});

Solution 2:[2]

You describe a classic race condition where 2 users create a different operation over the same resource (in our use-case - a record in db)

Short answer: record versioning - manage a unique version number for each record. changes can only be executed when deliver with the same version value

Long answer: Suppose we are using a no-sql db where every list is saved in lists collection where every record looks something like this:

{
  id: 'uuid-4',
  items: [
    { name: 'item #1', checked: false },
    { name: 'item #2', checked: true },
    { name: 'item #3', checked: false }
  ],
  version: 1648727895753
}

In the example above, I've use a timestamp as a record version (using the new Date().getTime() method). Every time this record is updated - the version changes as well.

Now - let's describe your scenario again:

2 users downloading the same todo list from server. At this point in time - both users have the same version value (let's say, version A). Now, let's assume that one user made an update while the other user is offline (in your example - due to traveling). In order for this pattern to work, we change the version value after each successful update so now the record has a new version value (let's say, version B). When the other user will try to make a change - it will be blocked because he still got the old version number (in our example A). In order to make a change - the newest version (B) should be downloaded first, then the user will update his own edits and write his changes to db (this process will result with an updated record with version C)

So in order to make this work, one should create the following mechanism:

Server Side (db logic)

  • on record update
    • make update only if request version value equals to record version value
  • on fetch record
    • return record with current version value

Client side (app logic)

  • on app load
    • fetch current todo list record (by id field)
  • on record update
    • send update to server with current version value
    • if update fails:
      • save current todo list record as old version
      • fetch current todo list record (by id field)
      • merge changes (you will decide how to reflect it to user)
      • send update to server with current version value (it's now the most updated one)

Technical Notes

  • Merging between 2 record versions (e.g. - between to JSON-based object) is a whole universe by its own. You may use a 3rd-party library to spot the differences (such as json-diff) or decide to apply a default behavior (for instance: if an item from list was deleted in db it will have a different style apply so the user understand that this item was removed)
  • Even now we still risk to have a race condition (well.. highly unlikely when using a simple todo list but may occur in application with high loads). For production ready setup - please consider using a message queue in order to apply all actions by a specific order (first in first out). Again - it's an overkill to your use-case but since we are solving here a more general problem - I think it's worth mentioning

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Guy Nachshon
Solution 2 ymz