How to update the data in the list (motoko)?

Help!!!
I have created a hashmap

var posts = HashMap.HashMap<Principal, Posts>(10, Principal.equal, Principal.hash);

where Posts is of type list.

public type Posts = List.List<Post>;

I have a function that create the list and put it into the HashMap.
But now I want to write a function to update the value of the List. But I haven’t found any function in the list to do so.
Is there any way to achieve it?

According to here: HashMap | Internet Computer Home
you have to use posts.put(principal, new_list) to overwrite the entry.

List is a functional data structure, so when you edit it you will get a new list and you have to put that new list into the hash map, overwriting the previous entry in the hash map.

Well, I am finally able to solve this. But it gave me a very big pain. I wonder how much I have to think and code just to update the value in my list. Such a pain for such a small task.

Others can help you better if you post your code. It should not be a pain. But we don’t know enough details.

Assuming the value you want to update is in the list already, then you need some way to know which it is. That is to say, each Post could likely could use an id, then you can get the array of posts from your posts hashmap, then use the map function (which returns each element of a set with a new element of the same type) against matching post id to update it. We use Opt type for each field of the post, and if it is present, overwrite the new post we are going to update the list with; otherwise copy the existing value…

assuming Posts looks like

type Post = {
  id: Text; // should probably be a specific Type like Types.PostId even if it is a Text or number
  title: Text;
  content: Text;
};

Note existingPosts has ALREADY been retrieved from the postsHashmap, and converted
from it’s optional type (see below).

// generic function that overwrites any existing values if new values are present
func editPost(
      existingPosts: [Types.Post],
      targetPostId: Text, 
      titleIn: ?Text,
      contentIn: ?Text,
    ): [Types.Post] {
     // there's probably a better way to do this, but it assumes the [Post] is going to contain a Post with the id given
      var throwError: Bool = true;
      
      let updateFcn = func(post: Types.Post): Types.Post {
        // so we are iterating over the [Posts] returning each post, if it has the id
       // overwrite existing values with given values, or just copy previous values
        if (post.id == targetPostId) {
          throwError := false; 
          return {
            id = targetPostId;
            title = Option.get<Text>(titleIn, post.title);
            content = Option.get<Text>(contentIn, post.content);
          };
        } else { post }; // don't update a post not updated
      };
      // now using the function above
      let updated = Array.map<Types.Post, Types.Post>(existingPosts, updateFcn);
      assert(not throwError); 
      return updated; // since a post was found with the id, don't trap and return the updated list
    };
  };

To originally get the [Post] you can use this:

    public func getPosts(forPrincipal: Principal): [Types.Post] {
      assert(principalMaps(forPrincipal)); 
      let exists = postsHashMap.get(forPrincipal);
      switch (exists) {
        case (null) { Prelude.unreachable(); }; // assumes the principal ALREADY exists in this map as a key
        case (?exists) { return exists };
      };
    };

Now you can pass getPosts into the update function above, with the optional fields that need to be updated. Whatever other fields you want to add, you can just add them to the Post type and then as optional values to the update function, and pass them in if they change otherwise pass null.

Another way to do this is freeze/unfreeze the array, I’m not sure which is more performant though. Also this is from another project, and I was working on the assumption that principals/post ids would be checked and verified as already existing before retrieved from the map/to be updated, which is why I trap above if there’s no post found with corresponding id.

2 Likes

In my solution I have also written the similar code but this looks more useful since it give you the option to update any field. It helps a lot, Thanks.