Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to optimistically update item in a ListView

I'm making a simple react native app that has several lists and it gives the user the opportunity to update an item in any of those lists or to add new items to the list.

However, as has been previously pointed out on StackOverflow, one can't add or push an item to a ListView because it's supposed to be an immutable data structure. i.e. in this code one cannot push a new item into the data source

var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
var data = ds.cloneWithRows(responseData)
var obj = {};
obj['someproperty'] = 'foo';
data.push(obj); //cannot push a new item into data

So what I've done instead is to keep a copy (this.state.items; in code below) of the data I fetch from the server (and which I call cloneWithRows on), append to that copy (or update it if it's an edit), and then call cloneWithRows on the mutated copy any time there's a change (either through an edit or creation of a new item) and update the ui, and then update the server (this last part outside the scope of the react native app).. So in summary, I have a copy of the data that I can mutate to use as a source of data for ds.cloneWithRows when I make a change

However, it doesn't seem to make much sense to keep a cache of the data structure that I can mutate for the sole purpose of using it as the source for the immutable data structure (listForUI: ds.cloneWithRows(t) in the code below) if I want to optimistically update the UI (i.e. show the change before its saved on the server).

The alternative (also not making much sense to me) would seem to be to update the server, and wait for a response (without keeping a cache of any kind) any time I update or add a new item to my list, but this would be slow?

Question: Short of keeping a mutable copy of the data as a cache, is there a way to optimistically update a ui in react native?

addNewItemToList: function(){

  var t = this.state.items;  //my cache of list items
  var newListItem = {};
  newListItem['id'] = 123;
  t.push(newListItem);  //adding-mutating the cache

  //creating a new immutable ds
  var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
  this.setState({
    listForUI: ds.cloneWithRows(t)
  }, function(){

     //update server with change
     fetch('http://localhost:8080/accounts/1/lists/3/todos', {
        method: 'POST',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
        //code ommitted
  }

 }
like image 818
Leahcim Avatar asked Feb 26 '16 17:02

Leahcim


2 Answers

No, I'm pretty sure that there isn't any other way to update a ListView datasource, since as you explain, it's immutable. Immutability implies that you will always need to make a copy of anything before adding to it.

You mention:

However, it doesn't seem to make much sense to keep a cache of the data structure that I can mutate for the sole purpose of using it as the source for the immutable data structure...

I'm not really sure why you feel that keeping this cache doesn't make sense. I don't see it being a cause for performance problems either, since the cache only exists for a brief moment while addNewItemToList is being run.

like image 79
Dave Sibiski Avatar answered Oct 25 '22 22:10

Dave Sibiski


You will need to store the previous items if you want to undo an optimistic update. To do so you will need to "cache" or store a reference to the previous data somehow.

You can optimistically call this.setState in your addNewItemToList handler and update your state (causing a re-render), and then in your fetch you can revert the change if necessary (ie if the call fails):

addNewItemToList: function(id) {
    // Store current list of items and calculate new list
    var prevItems = this.state.listForUI.slice();
    var newItems = this.state.items.concat([id]);

    // Optimistically set new items list; will cause a re-render
    this.setState({
        listForUI: this.state.listForUI.cloneWithRows(newItems)
    });

    // Update server with change
    fetch('http://localhost:8080/accounts/1/lists/3/todos', {
        method: 'POST',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
        //code ommitted
     })

     // Catch an error and revert change
     .catch(function (ex) {
        this.setState({
             listForUI: this.state.listForUI.cloneWithRows(prevItems)
         });
     });
 }
like image 1
Calvin Belden Avatar answered Oct 25 '22 22:10

Calvin Belden